1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from __future__ import absolute_import
6
7import re
8import os
9from urlparse import urlparse
10import mozpack.path as mozpath
11from mozpack.chrome.flags import Flags
12from mozpack.errors import errors
13
14
15class ManifestEntry(object):
16    '''
17    Base class for all manifest entry types.
18    Subclasses may define the following class or member variables:
19        - localized: indicates whether the manifest entry is used for localized
20          data.
21        - type: the manifest entry type (e.g. 'content' in
22          'content global content/global/')
23        - allowed_flags: a set of flags allowed to be defined for the given
24          manifest entry type.
25
26    A manifest entry is attached to a base path, defining where the manifest
27    entry is bound to, and that is used to find relative paths defined in
28    entries.
29    '''
30    localized = False
31    type = None
32    allowed_flags = [
33        'application',
34        'platformversion',
35        'os',
36        'osversion',
37        'abi',
38        'xpcnativewrappers',
39        'tablet',
40        'process',
41        'contentaccessible',
42    ]
43
44    def __init__(self, base, *flags):
45        '''
46        Initialize a manifest entry with the given base path and flags.
47        '''
48        self.base = base
49        self.flags = Flags(*flags)
50        if not all(f in self.allowed_flags for f in self.flags):
51            errors.fatal('%s unsupported for %s manifest entries' %
52                         (','.join(f for f in self.flags
53                          if not f in self.allowed_flags), self.type))
54
55    def serialize(self, *args):
56        '''
57        Serialize the manifest entry.
58        '''
59        entry = [self.type] + list(args)
60        flags = str(self.flags)
61        if flags:
62            entry.append(flags)
63        return ' '.join(entry)
64
65    def __eq__(self, other):
66        return self.base == other.base and str(self) == str(other)
67
68    def __ne__(self, other):
69        return not self.__eq__(other)
70
71    def __repr__(self):
72        return '<%s@%s>' % (str(self), self.base)
73
74    def move(self, base):
75        '''
76        Return a new manifest entry with a different base path.
77        '''
78        return parse_manifest_line(base, str(self))
79
80    def rebase(self, base):
81        '''
82        Return a new manifest entry with all relative paths defined in the
83        entry relative to a new base directory.
84        The base class doesn't define relative paths, so it is equivalent to
85        move().
86        '''
87        return self.move(base)
88
89
90class ManifestEntryWithRelPath(ManifestEntry):
91    '''
92    Abstract manifest entry type with a relative path definition.
93    '''
94    def __init__(self, base, relpath, *flags):
95        ManifestEntry.__init__(self, base, *flags)
96        self.relpath = relpath
97
98    def __str__(self):
99        return self.serialize(self.relpath)
100
101    def rebase(self, base):
102        '''
103        Return a new manifest entry with all relative paths defined in the
104        entry relative to a new base directory.
105        '''
106        clone = ManifestEntry.rebase(self, base)
107        clone.relpath = mozpath.rebase(self.base, base, self.relpath)
108        return clone
109
110    @property
111    def path(self):
112        return mozpath.normpath(mozpath.join(self.base,
113                                                       self.relpath))
114
115
116class Manifest(ManifestEntryWithRelPath):
117    '''
118    Class for 'manifest' entries.
119        manifest some/path/to/another.manifest
120    '''
121    type = 'manifest'
122
123
124class ManifestChrome(ManifestEntryWithRelPath):
125    '''
126    Abstract class for chrome entries.
127    '''
128    def __init__(self, base, name, relpath, *flags):
129        ManifestEntryWithRelPath.__init__(self, base, relpath, *flags)
130        self.name = name
131
132    @property
133    def location(self):
134        return mozpath.join(self.base, self.relpath)
135
136
137class ManifestContent(ManifestChrome):
138    '''
139    Class for 'content' entries.
140        content global content/global/
141    '''
142    type = 'content'
143    allowed_flags = ManifestChrome.allowed_flags + [
144        'contentaccessible',
145        'platform',
146    ]
147
148    def __str__(self):
149        return self.serialize(self.name, self.relpath)
150
151
152class ManifestMultiContent(ManifestChrome):
153    '''
154    Abstract class for chrome entries with multiple definitions.
155    Used for locale and skin entries.
156    '''
157    type = None
158
159    def __init__(self, base, name, id, relpath, *flags):
160        ManifestChrome.__init__(self, base, name, relpath, *flags)
161        self.id = id
162
163    def __str__(self):
164        return self.serialize(self.name, self.id, self.relpath)
165
166
167class ManifestLocale(ManifestMultiContent):
168    '''
169    Class for 'locale' entries.
170        locale global en-US content/en-US/
171        locale global fr content/fr/
172    '''
173    localized = True
174    type = 'locale'
175
176
177class ManifestSkin(ManifestMultiContent):
178    '''
179    Class for 'skin' entries.
180        skin global classic/1.0 content/skin/classic/
181    '''
182    type = 'skin'
183
184
185class ManifestOverload(ManifestEntry):
186    '''
187    Abstract class for chrome entries defining some kind of overloading.
188    Used for overlay, override or style entries.
189    '''
190    type = None
191
192    def __init__(self, base, overloaded, overload, *flags):
193        ManifestEntry.__init__(self, base, *flags)
194        self.overloaded = overloaded
195        self.overload = overload
196
197    def __str__(self):
198        return self.serialize(self.overloaded, self.overload)
199
200
201class ManifestOverlay(ManifestOverload):
202    '''
203    Class for 'overlay' entries.
204        overlay chrome://global/content/viewSource.xul \
205            chrome://browser/content/viewSourceOverlay.xul
206    '''
207    type = 'overlay'
208
209
210class ManifestStyle(ManifestOverload):
211    '''
212    Class for 'style' entries.
213        style chrome://global/content/viewSource.xul \
214            chrome://browser/skin/
215    '''
216    type = 'style'
217
218
219class ManifestOverride(ManifestOverload):
220    '''
221    Class for 'override' entries.
222        override chrome://global/locale/netError.dtd \
223            chrome://browser/locale/netError.dtd
224    '''
225    type = 'override'
226
227
228class ManifestResource(ManifestEntry):
229    '''
230    Class for 'resource' entries.
231        resource gre-resources toolkit/res/
232        resource services-sync resource://gre/modules/services-sync/
233
234    The target may be a relative path or a resource or chrome url.
235    '''
236    type = 'resource'
237
238    def __init__(self, base, name, target, *flags):
239        ManifestEntry.__init__(self, base, *flags)
240        self.name = name
241        self.target = target
242
243    def __str__(self):
244        return self.serialize(self.name, self.target)
245
246    def rebase(self, base):
247        u = urlparse(self.target)
248        if u.scheme and u.scheme != 'jar':
249            return ManifestEntry.rebase(self, base)
250        clone = ManifestEntry.rebase(self, base)
251        clone.target = mozpath.rebase(self.base, base, self.target)
252        return clone
253
254
255class ManifestBinaryComponent(ManifestEntryWithRelPath):
256    '''
257    Class for 'binary-component' entries.
258        binary-component some/path/to/a/component.dll
259    '''
260    type = 'binary-component'
261
262
263class ManifestComponent(ManifestEntryWithRelPath):
264    '''
265    Class for 'component' entries.
266        component {b2bba4df-057d-41ea-b6b1-94a10a8ede68} foo.js
267    '''
268    type = 'component'
269
270    def __init__(self, base, cid, file, *flags):
271        ManifestEntryWithRelPath.__init__(self, base, file, *flags)
272        self.cid = cid
273
274    def __str__(self):
275        return self.serialize(self.cid, self.relpath)
276
277
278class ManifestInterfaces(ManifestEntryWithRelPath):
279    '''
280    Class for 'interfaces' entries.
281        interfaces foo.xpt
282    '''
283    type = 'interfaces'
284
285
286class ManifestCategory(ManifestEntry):
287    '''
288    Class for 'category' entries.
289        category command-line-handler m-browser @mozilla.org/browser/clh;
290    '''
291    type = 'category'
292
293    def __init__(self, base, category, name, value, *flags):
294        ManifestEntry.__init__(self, base, *flags)
295        self.category = category
296        self.name = name
297        self.value = value
298
299    def __str__(self):
300        return self.serialize(self.category, self.name, self.value)
301
302
303class ManifestContract(ManifestEntry):
304    '''
305    Class for 'contract' entries.
306        contract @mozilla.org/foo;1 {b2bba4df-057d-41ea-b6b1-94a10a8ede68}
307    '''
308    type = 'contract'
309
310    def __init__(self, base, contractID, cid, *flags):
311        ManifestEntry.__init__(self, base, *flags)
312        self.contractID = contractID
313        self.cid = cid
314
315    def __str__(self):
316        return self.serialize(self.contractID, self.cid)
317
318# All manifest classes by their type name.
319MANIFESTS_TYPES = dict([(c.type, c) for c in globals().values()
320                       if type(c) == type and issubclass(c, ManifestEntry)
321                       and hasattr(c, 'type') and c.type])
322
323MANIFEST_RE = re.compile(r'^#.*$')
324
325
326def parse_manifest_line(base, line):
327    '''
328    Parse a line from a manifest file with the given base directory and
329    return the corresponding ManifestEntry instance.
330    '''
331    # Remove comments
332    cmd = MANIFEST_RE.sub('', line).strip().split()
333    if not cmd:
334        return None
335    if not cmd[0] in MANIFESTS_TYPES:
336        return errors.fatal('Unknown manifest directive: %s' % cmd[0])
337    return MANIFESTS_TYPES[cmd[0]](base, *cmd[1:])
338
339
340def parse_manifest(root, path, fileobj=None):
341    '''
342    Parse a manifest file.
343    '''
344    base = mozpath.dirname(path)
345    if root:
346        path = os.path.normpath(os.path.abspath(os.path.join(root, path)))
347    if not fileobj:
348        fileobj = open(path)
349    linenum = 0
350    for line in fileobj:
351        linenum += 1
352        with errors.context(path, linenum):
353            e = parse_manifest_line(base, line)
354            if e:
355                yield e
356
357
358def is_manifest(path):
359    '''
360    Return whether the given path is that of a manifest file.
361    '''
362    return path.endswith('.manifest') and not path.endswith('.CRT.manifest') \
363        and not path.endswith('.exe.manifest')
364