michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import re michael@0: import os michael@0: from urlparse import urlparse michael@0: import mozpack.path michael@0: from mozpack.chrome.flags import Flags michael@0: from mozpack.errors import errors michael@0: michael@0: michael@0: class ManifestEntry(object): michael@0: ''' michael@0: Base class for all manifest entry types. michael@0: Subclasses may define the following class or member variables: michael@0: - localized: indicates whether the manifest entry is used for localized michael@0: data. michael@0: - type: the manifest entry type (e.g. 'content' in michael@0: 'content global content/global/') michael@0: - allowed_flags: a set of flags allowed to be defined for the given michael@0: manifest entry type. michael@0: michael@0: A manifest entry is attached to a base path, defining where the manifest michael@0: entry is bound to, and that is used to find relative paths defined in michael@0: entries. michael@0: ''' michael@0: localized = False michael@0: type = None michael@0: allowed_flags = [ michael@0: 'application', michael@0: 'platformversion', michael@0: 'os', michael@0: 'osversion', michael@0: 'abi', michael@0: 'xpcnativewrappers', michael@0: 'tablet', michael@0: ] michael@0: michael@0: def __init__(self, base, *flags): michael@0: ''' michael@0: Initialize a manifest entry with the given base path and flags. michael@0: ''' michael@0: self.base = base michael@0: self.flags = Flags(*flags) michael@0: if not all(f in self.allowed_flags for f in self.flags): michael@0: errors.fatal('%s unsupported for %s manifest entries' % michael@0: (','.join(f for f in self.flags michael@0: if not f in self.allowed_flags), self.type)) michael@0: michael@0: def serialize(self, *args): michael@0: ''' michael@0: Serialize the manifest entry. michael@0: ''' michael@0: entry = [self.type] + list(args) michael@0: flags = str(self.flags) michael@0: if flags: michael@0: entry.append(flags) michael@0: return ' '.join(entry) michael@0: michael@0: def __eq__(self, other): michael@0: return self.base == other.base and str(self) == str(other) michael@0: michael@0: def __ne__(self, other): michael@0: return not self.__eq__(other) michael@0: michael@0: def __repr__(self): michael@0: return '<%s@%s>' % (str(self), self.base) michael@0: michael@0: def move(self, base): michael@0: ''' michael@0: Return a new manifest entry with a different base path. michael@0: ''' michael@0: return parse_manifest_line(base, str(self)) michael@0: michael@0: def rebase(self, base): michael@0: ''' michael@0: Return a new manifest entry with all relative paths defined in the michael@0: entry relative to a new base directory. michael@0: The base class doesn't define relative paths, so it is equivalent to michael@0: move(). michael@0: ''' michael@0: return self.move(base) michael@0: michael@0: michael@0: class ManifestEntryWithRelPath(ManifestEntry): michael@0: ''' michael@0: Abstract manifest entry type with a relative path definition. michael@0: ''' michael@0: def __init__(self, base, relpath, *flags): michael@0: ManifestEntry.__init__(self, base, *flags) michael@0: self.relpath = relpath michael@0: michael@0: def __str__(self): michael@0: return self.serialize(self.relpath) michael@0: michael@0: def rebase(self, base): michael@0: ''' michael@0: Return a new manifest entry with all relative paths defined in the michael@0: entry relative to a new base directory. michael@0: ''' michael@0: clone = ManifestEntry.rebase(self, base) michael@0: clone.relpath = mozpack.path.rebase(self.base, base, self.relpath) michael@0: return clone michael@0: michael@0: @property michael@0: def path(self): michael@0: return mozpack.path.normpath(mozpack.path.join(self.base, michael@0: self.relpath)) michael@0: michael@0: michael@0: class Manifest(ManifestEntryWithRelPath): michael@0: ''' michael@0: Class for 'manifest' entries. michael@0: manifest some/path/to/another.manifest michael@0: ''' michael@0: type = 'manifest' michael@0: michael@0: michael@0: class ManifestChrome(ManifestEntryWithRelPath): michael@0: ''' michael@0: Abstract class for chrome entries. michael@0: ''' michael@0: def __init__(self, base, name, relpath, *flags): michael@0: ManifestEntryWithRelPath.__init__(self, base, relpath, *flags) michael@0: self.name = name michael@0: michael@0: @property michael@0: def location(self): michael@0: return mozpack.path.join(self.base, self.relpath) michael@0: michael@0: michael@0: class ManifestContent(ManifestChrome): michael@0: ''' michael@0: Class for 'content' entries. michael@0: content global content/global/ michael@0: ''' michael@0: type = 'content' michael@0: allowed_flags = ManifestChrome.allowed_flags + [ michael@0: 'contentaccessible', michael@0: 'platform', michael@0: ] michael@0: michael@0: def __str__(self): michael@0: return self.serialize(self.name, self.relpath) michael@0: michael@0: michael@0: class ManifestMultiContent(ManifestChrome): michael@0: ''' michael@0: Abstract class for chrome entries with multiple definitions. michael@0: Used for locale and skin entries. michael@0: ''' michael@0: type = None michael@0: michael@0: def __init__(self, base, name, id, relpath, *flags): michael@0: ManifestChrome.__init__(self, base, name, relpath, *flags) michael@0: self.id = id michael@0: michael@0: def __str__(self): michael@0: return self.serialize(self.name, self.id, self.relpath) michael@0: michael@0: michael@0: class ManifestLocale(ManifestMultiContent): michael@0: ''' michael@0: Class for 'locale' entries. michael@0: locale global en-US content/en-US/ michael@0: locale global fr content/fr/ michael@0: ''' michael@0: localized = True michael@0: type = 'locale' michael@0: michael@0: michael@0: class ManifestSkin(ManifestMultiContent): michael@0: ''' michael@0: Class for 'skin' entries. michael@0: skin global classic/1.0 content/skin/classic/ michael@0: ''' michael@0: type = 'skin' michael@0: michael@0: michael@0: class ManifestOverload(ManifestEntry): michael@0: ''' michael@0: Abstract class for chrome entries defining some kind of overloading. michael@0: Used for overlay, override or style entries. michael@0: ''' michael@0: type = None michael@0: michael@0: def __init__(self, base, overloaded, overload, *flags): michael@0: ManifestEntry.__init__(self, base, *flags) michael@0: self.overloaded = overloaded michael@0: self.overload = overload michael@0: michael@0: def __str__(self): michael@0: return self.serialize(self.overloaded, self.overload) michael@0: michael@0: @property michael@0: def localized(self): michael@0: u = urlparse(self.overload) michael@0: return u.scheme == 'chrome' and \ michael@0: u.path.split('/')[0:2] == ['', 'locale'] michael@0: michael@0: michael@0: class ManifestOverlay(ManifestOverload): michael@0: ''' michael@0: Class for 'overlay' entries. michael@0: overlay chrome://global/content/viewSource.xul \ michael@0: chrome://browser/content/viewSourceOverlay.xul michael@0: ''' michael@0: type = 'overlay' michael@0: michael@0: michael@0: class ManifestStyle(ManifestOverload): michael@0: ''' michael@0: Class for 'style' entries. michael@0: style chrome://global/content/customizeToolbar.xul \ michael@0: chrome://browser/skin/ michael@0: ''' michael@0: type = 'style' michael@0: michael@0: michael@0: class ManifestOverride(ManifestOverload): michael@0: ''' michael@0: Class for 'override' entries. michael@0: override chrome://global/locale/netError.dtd \ michael@0: chrome://browser/locale/netError.dtd michael@0: ''' michael@0: type = 'override' michael@0: michael@0: michael@0: class ManifestResource(ManifestEntry): michael@0: ''' michael@0: Class for 'resource' entries. michael@0: resource gre-resources toolkit/res/ michael@0: resource services-sync resource://gre/modules/services-sync/ michael@0: michael@0: The target may be a relative path or a resource or chrome url. michael@0: ''' michael@0: type = 'resource' michael@0: michael@0: def __init__(self, base, name, target, *flags): michael@0: ManifestEntry.__init__(self, base, *flags) michael@0: self.name = name michael@0: self.target = target michael@0: michael@0: def __str__(self): michael@0: return self.serialize(self.name, self.target) michael@0: michael@0: def rebase(self, base): michael@0: u = urlparse(self.target) michael@0: if u.scheme and u.scheme != 'jar': michael@0: return ManifestEntry.rebase(self, base) michael@0: clone = ManifestEntry.rebase(self, base) michael@0: clone.target = mozpack.path.rebase(self.base, base, self.target) michael@0: return clone michael@0: michael@0: michael@0: class ManifestBinaryComponent(ManifestEntryWithRelPath): michael@0: ''' michael@0: Class for 'binary-component' entries. michael@0: binary-component some/path/to/a/component.dll michael@0: ''' michael@0: type = 'binary-component' michael@0: michael@0: michael@0: class ManifestComponent(ManifestEntryWithRelPath): michael@0: ''' michael@0: Class for 'component' entries. michael@0: component {b2bba4df-057d-41ea-b6b1-94a10a8ede68} foo.js michael@0: ''' michael@0: type = 'component' michael@0: michael@0: def __init__(self, base, cid, file, *flags): michael@0: ManifestEntryWithRelPath.__init__(self, base, file, *flags) michael@0: self.cid = cid michael@0: michael@0: def __str__(self): michael@0: return self.serialize(self.cid, self.relpath) michael@0: michael@0: michael@0: class ManifestInterfaces(ManifestEntryWithRelPath): michael@0: ''' michael@0: Class for 'interfaces' entries. michael@0: interfaces foo.xpt michael@0: ''' michael@0: type = 'interfaces' michael@0: michael@0: michael@0: class ManifestCategory(ManifestEntry): michael@0: ''' michael@0: Class for 'category' entries. michael@0: category command-line-handler m-browser @mozilla.org/browser/clh; michael@0: ''' michael@0: type = 'category' michael@0: michael@0: def __init__(self, base, category, name, value, *flags): michael@0: ManifestEntry.__init__(self, base, *flags) michael@0: self.category = category michael@0: self.name = name michael@0: self.value = value michael@0: michael@0: def __str__(self): michael@0: return self.serialize(self.category, self.name, self.value) michael@0: michael@0: michael@0: class ManifestContract(ManifestEntry): michael@0: ''' michael@0: Class for 'contract' entries. michael@0: contract @mozilla.org/foo;1 {b2bba4df-057d-41ea-b6b1-94a10a8ede68} michael@0: ''' michael@0: type = 'contract' michael@0: michael@0: def __init__(self, base, contractID, cid, *flags): michael@0: ManifestEntry.__init__(self, base, *flags) michael@0: self.contractID = contractID michael@0: self.cid = cid michael@0: michael@0: def __str__(self): michael@0: return self.serialize(self.contractID, self.cid) michael@0: michael@0: # All manifest classes by their type name. michael@0: MANIFESTS_TYPES = dict([(c.type, c) for c in globals().values() michael@0: if type(c) == type and issubclass(c, ManifestEntry) michael@0: and hasattr(c, 'type') and c.type]) michael@0: michael@0: MANIFEST_RE = re.compile(r'\s*#.*$') michael@0: michael@0: michael@0: def parse_manifest_line(base, line): michael@0: ''' michael@0: Parse a line from a manifest file with the given base directory and michael@0: return the corresponding ManifestEntry instance. michael@0: ''' michael@0: # Remove comments michael@0: cmd = MANIFEST_RE.sub('', line).strip().split() michael@0: if not cmd: michael@0: return None michael@0: if not cmd[0] in MANIFESTS_TYPES: michael@0: return errors.fatal('Unknown manifest directive: %s' % cmd[0]) michael@0: return MANIFESTS_TYPES[cmd[0]](base, *cmd[1:]) michael@0: michael@0: michael@0: def parse_manifest(root, path, fileobj=None): michael@0: ''' michael@0: Parse a manifest file. michael@0: ''' michael@0: base = mozpack.path.dirname(path) michael@0: if root: michael@0: path = os.path.normpath(os.path.abspath(os.path.join(root, path))) michael@0: if not fileobj: michael@0: fileobj = open(path) michael@0: linenum = 0 michael@0: for line in fileobj: michael@0: linenum += 1 michael@0: with errors.context(path, linenum): michael@0: e = parse_manifest_line(base, line) michael@0: if e: michael@0: yield e michael@0: michael@0: michael@0: def is_manifest(path): michael@0: ''' michael@0: Return whether the given path is that of a manifest file. michael@0: ''' michael@0: return path.endswith('.manifest') and not path.endswith('.CRT.manifest') \ michael@0: and not path.endswith('.exe.manifest')