def process_depends(item, local_feed_dir): """Internal""" # Note: also called from selections # Note: used by 0compile attrs = item.attrs dep_iface = item.getAttribute('interface') if not dep_iface: raise InvalidInterface(_("Missing 'interface' on <%s>") % item.name) if dep_iface.startswith('.'): if local_feed_dir: dep_iface = os.path.abspath(os.path.join(local_feed_dir, dep_iface)) # (updates the element too, in case we write it out again) attrs['interface'] = dep_iface else: raise InvalidInterface(_('Relative interface URI "%s" in non-local feed') % dep_iface) if item.name == 'restricts': dependency = InterfaceRestriction(dep_iface, element = item) else: dependency = InterfaceDependency(dep_iface, element = item) version = item.getAttribute('version') if version: try: r = VersionExpressionRestriction(version) except SafeException as ex: msg = "Can't parse version restriction '{version}': {error}".format(version = version, error = ex) logger.warning(msg) r = ImpossibleRestriction(msg) dependency.restrictions.append(r) distro = item.getAttribute('distribution') if distro: dependency.restrictions.append(DistributionRestriction(distro)) for e in item.childNodes: if e.uri != XMLNS_IFACE: continue if e.name in binding_names: dependency.bindings.append(process_binding(e)) elif e.name == 'version': dependency.restrictions.append( VersionRangeRestriction(not_before = parse_version(e.getAttribute('not-before')), before = parse_version(e.getAttribute('before')))) return dependency
def process_depends(item, local_feed_dir): """Internal""" # Note: also called from selections # Note: used by 0compile attrs = item.attrs dep_iface = item.getAttribute('interface') if not dep_iface: raise InvalidInterface(_("Missing 'interface' on <%s>") % item.name) if dep_iface.startswith('.'): if local_feed_dir: dep_iface = os.path.abspath(os.path.join(local_feed_dir, dep_iface)) # (updates the element too, in case we write it out again) attrs['interface'] = dep_iface else: raise InvalidInterface(_('Relative interface URI "%s" in non-local feed') % dep_iface) if item.name == 'restricts': dependency = InterfaceRestriction(dep_iface, element = item) else: dependency = InterfaceDependency(dep_iface, element = item) version = item.getAttribute('version') if version: try: r = VersionExpressionRestriction(version) except SafeException as ex: msg = "Can't parse version restriction '{version}': {error}".format(version = version, error = ex) logger.warn(msg) r = ImpossibleRestriction(msg) dependency.restrictions.append(r) for e in item.childNodes: if e.uri != XMLNS_IFACE: continue if e.name in binding_names: dependency.bindings.append(process_binding(e)) elif e.name == 'version': dependency.restrictions.append( VersionRangeRestriction(not_before = parse_version(e.getAttribute('not-before')), before = parse_version(e.getAttribute('before')))) return dependency
def __init__(self, feed_element, local_path = None): """Create a feed object from a DOM. @param feed_element: the root element of a feed file @type feed_element: L{qdom.Element} @param local_path: the pathname of this local feed, or None for remote feeds @type local_path: str | None""" self.local_path = local_path self.implementations = {} self.name = None self.summaries = {} # { lang: str } self.first_summary = None self.last_modified = None self.feeds = [] self.metadata = [] self.feed_element = feed_element if feed_element is None: return # XXX subclass? if feed_element.name not in ('interface', 'feed'): raise SafeException("Root element should be <interface>, not <%s>" % feed_element.name) assert feed_element.uri == XMLNS_IFACE, "Wrong namespace on root element: %s" % feed_element.uri if local_path: self.url = local_path else: assert local_path is None self.url = feed_element.getAttribute('uri') if not self.url: raise InvalidInterface(_("<interface> uri attribute missing")) min_injector_version = feed_element.getAttribute('min-injector-version') if min_injector_version: if parse_version(min_injector_version) > parse_version(version): raise InvalidInterface(_("This feed requires version %(min_version)s or later of " "Zero Install, but I am only version %(version)s. " "You can get a newer version from http://0install.net") % {'min_version': min_injector_version, 'version': version}) for x in feed_element.childNodes: if x.uri != XMLNS_IFACE: self.metadata.append(x) continue if x.name == 'name': self.name = x.content elif x.name == 'description': pass elif x.name == 'summary': if self.first_summary == None: self.first_summary = x.content self.summaries[x.attrs.get("http://www.w3.org/XML/1998/namespace lang", 'en')] = x.content elif x.name == 'feed-for': pass elif x.name == 'feed': pass else: self.metadata.append(x) if not self.name: raise InvalidInterface(_("Missing <name> in feed")) if not self.summary: raise InvalidInterface(_("Missing <summary> in feed"))
def __init__(self, feed_element, local_path = None, distro = None): """Create a feed object from a DOM. @param feed_element: the root element of a feed file @type feed_element: L{qdom.Element} @param local_path: the pathname of this local feed, or None for remote feeds""" self.local_path = local_path self.implementations = {} self.name = None self.summaries = {} # { lang: str } self.first_summary = None self.descriptions = {} # { lang: str } self.first_description = None self.last_modified = None self.feeds = [] self.feed_for = set() self.metadata = [] self.last_checked = None self._package_implementations = [] if distro is not None: import warnings warnings.warn("distro argument is now ignored", DeprecationWarning, 2) if feed_element is None: return # XXX subclass? assert feed_element.name in ('interface', 'feed'), "Root element should be <interface>, not %s" % feed_element assert feed_element.uri == XMLNS_IFACE, "Wrong namespace on root element: %s" % feed_element.uri main = feed_element.getAttribute('main') #if main: warn("Setting 'main' on the root element is deprecated. Put it on a <group> instead") if local_path: self.url = local_path local_dir = os.path.dirname(local_path) else: assert local_path is None self.url = feed_element.getAttribute('uri') if not self.url: raise InvalidInterface(_("<interface> uri attribute missing")) local_dir = None # Can't have relative paths min_injector_version = feed_element.getAttribute('min-injector-version') if min_injector_version: if parse_version(min_injector_version) > parse_version(version): raise InvalidInterface(_("This feed requires version %(min_version)s or later of " "Zero Install, but I am only version %(version)s. " "You can get a newer version from http://0install.net") % {'min_version': min_injector_version, 'version': version}) for x in feed_element.childNodes: if x.uri != XMLNS_IFACE: self.metadata.append(x) continue if x.name == 'name': self.name = x.content elif x.name == 'description': if self.first_description == None: self.first_description = x.content self.descriptions[x.attrs.get("http://www.w3.org/XML/1998/namespace lang", 'en')] = x.content elif x.name == 'summary': if self.first_summary == None: self.first_summary = x.content self.summaries[x.attrs.get("http://www.w3.org/XML/1998/namespace lang", 'en')] = x.content elif x.name == 'feed-for': feed_iface = x.getAttribute('interface') if not feed_iface: raise InvalidInterface(_('Missing "interface" attribute in <feed-for>')) self.feed_for.add(feed_iface) # Bug report from a Debian/stable user that --feed gets the wrong value. # Can't reproduce (even in a Debian/stable chroot), but add some logging here # in case it happens again. logger.debug(_("Is feed-for %s"), feed_iface) elif x.name == 'feed': feed_src = x.getAttribute('src') if not feed_src: raise InvalidInterface(_('Missing "src" attribute in <feed>')) if feed_src.startswith('http:') or feed_src.startswith('https:') or local_path: if feed_src.startswith('.'): feed_src = os.path.abspath(os.path.join(local_dir, feed_src)) langs = x.getAttribute('langs') if langs: langs = langs.replace('_', '-') self.feeds.append(Feed(feed_src, x.getAttribute('arch'), False, langs = langs)) else: raise InvalidInterface(_("Invalid feed URL '%s'") % feed_src) else: self.metadata.append(x) if not self.name: raise InvalidInterface(_("Missing <name> in feed")) if not self.summary: raise InvalidInterface(_("Missing <summary> in feed")) def process_group(group, group_attrs, base_depends, base_bindings, base_commands): for item in group.childNodes: if item.uri != XMLNS_IFACE: continue if item.name not in ('group', 'implementation', 'package-implementation'): continue # We've found a group or implementation. Scan for dependencies, # bindings and commands. Doing this here means that: # - We can share the code for groups and implementations here. # - The order doesn't matter, because these get processed first. # A side-effect is that the document root cannot contain # these. depends = base_depends[:] bindings = base_bindings[:] commands = base_commands.copy() for attr, command in [('main', 'run'), ('self-test', 'test')]: value = item.attrs.get(attr, None) if value is not None: commands[command] = Command(qdom.Element(XMLNS_IFACE, 'command', {'name': command, 'path': value}), None) for child in item.childNodes: if child.uri != XMLNS_IFACE: continue if child.name in _dependency_names: dep = process_depends(child, local_dir) depends.append(dep) elif child.name == 'command': command_name = child.attrs.get('name', None) if not command_name: raise InvalidInterface('Missing name for <command>') commands[command_name] = Command(child, local_dir) elif child.name in binding_names: bindings.append(process_binding(child)) compile_command = item.attrs.get('http://zero-install.sourceforge.net/2006/namespaces/0compile command') if compile_command is not None: commands['compile'] = Command(qdom.Element(XMLNS_IFACE, 'command', {'name': 'compile', 'shell-command': compile_command}), None) item_attrs = _merge_attrs(group_attrs, item) if item.name == 'group': process_group(item, item_attrs, depends, bindings, commands) elif item.name == 'implementation': process_impl(item, item_attrs, depends, bindings, commands) elif item.name == 'package-implementation': self._package_implementations.append((item, item_attrs, depends)) else: assert 0 def process_impl(item, item_attrs, depends, bindings, commands): id = item.getAttribute('id') if id is None: raise InvalidInterface(_("Missing 'id' attribute on %s") % item) local_path = item_attrs.get('local-path') if local_dir and local_path: abs_local_path = os.path.abspath(os.path.join(local_dir, local_path)) impl = ZeroInstallImplementation(self, id, abs_local_path) elif local_dir and (id.startswith('/') or id.startswith('.')): # For old feeds id = os.path.abspath(os.path.join(local_dir, id)) impl = ZeroInstallImplementation(self, id, id) else: impl = ZeroInstallImplementation(self, id, None) if '=' in id: # In older feeds, the ID was the (single) digest impl.digests.append(id) if id in self.implementations: logger.warn(_("Duplicate ID '%(id)s' in feed '%(feed)s'"), {'id': id, 'feed': self}) self.implementations[id] = impl impl.metadata = item_attrs try: version_mod = item_attrs.get('version-modifier', None) if version_mod: item_attrs['version'] += version_mod del item_attrs['version-modifier'] version = item_attrs['version'] except KeyError: raise InvalidInterface(_("Missing version attribute")) impl.version = parse_version(version) impl.commands = commands impl.released = item_attrs.get('released', None) impl.langs = item_attrs.get('langs', '').replace('_', '-') size = item.getAttribute('size') if size: impl.size = int(size) impl.arch = item_attrs.get('arch', None) try: stability = stability_levels[str(item_attrs['stability'])] except KeyError: stab = str(item_attrs['stability']) if stab != stab.lower(): raise InvalidInterface(_('Stability "%s" invalid - use lower case!') % item_attrs.stability) raise InvalidInterface(_('Stability "%s" invalid') % item_attrs['stability']) if stability >= preferred: raise InvalidInterface(_("Upstream can't set stability to preferred!")) impl.upstream_stability = stability impl.bindings = bindings impl.requires = depends for elem in item.childNodes: if elem.uri != XMLNS_IFACE: continue if elem.name == 'archive': url = elem.getAttribute('href') if not url: raise InvalidInterface(_("Missing href attribute on <archive>")) size = elem.getAttribute('size') if not size: raise InvalidInterface(_("Missing size attribute on <archive>")) impl.add_download_source(url = url, size = int(size), extract = elem.getAttribute('extract'), start_offset = _get_long(elem, 'start-offset'), type = elem.getAttribute('type')) elif elem.name == 'manifest-digest': for aname, avalue in elem.attrs.items(): if ' ' not in aname: impl.digests.append(zerostore.format_algorithm_digest_pair(aname, avalue)) elif elem.name == 'recipe': recipe = Recipe() for recipe_step in elem.childNodes: if recipe_step.uri == XMLNS_IFACE and recipe_step.name == 'archive': url = recipe_step.getAttribute('href') if not url: raise InvalidInterface(_("Missing href attribute on <archive>")) size = recipe_step.getAttribute('size') if not size: raise InvalidInterface(_("Missing size attribute on <archive>")) recipe.steps.append(DownloadSource(None, url = url, size = int(size), extract = recipe_step.getAttribute('extract'), start_offset = _get_long(recipe_step, 'start-offset'), type = recipe_step.getAttribute('type'))) elif recipe_step.uri == XMLNS_IFACE and recipe_step.name == 'rename': source = recipe_step.getAttribute('source') if not source: raise InvalidInterface(_("Missing source attribute on <rename>")) dest = recipe_step.getAttribute('dest') if not dest: raise InvalidInterface(_("Missing dest attribute on <rename>")) recipe.steps.append(RenameStep(source=source, dest=dest)) else: logger.info(_("Unknown step '%s' in recipe; skipping recipe"), recipe_step.name) break else: impl.download_sources.append(recipe) root_attrs = {'stability': 'testing'} root_commands = {} if main: logger.info("Note: @main on document element is deprecated in %s", self) root_commands['run'] = Command(qdom.Element(XMLNS_IFACE, 'command', {'path': main, 'name': 'run'}), None) process_group(feed_element, root_attrs, [], [], root_commands)
def process_impl(item, item_attrs, depends, bindings, commands): id = item.getAttribute('id') if id is None: raise InvalidInterface(_("Missing 'id' attribute on %s") % item) local_path = item_attrs.get('local-path') if local_dir and local_path: abs_local_path = os.path.abspath(os.path.join(local_dir, local_path)) impl = ZeroInstallImplementation(self, id, abs_local_path) elif local_dir and (id.startswith('/') or id.startswith('.')): # For old feeds id = os.path.abspath(os.path.join(local_dir, id)) impl = ZeroInstallImplementation(self, id, id) else: impl = ZeroInstallImplementation(self, id, None) if '=' in id: # In older feeds, the ID was the (single) digest impl.digests.append(id) if id in self.implementations: logger.warn(_("Duplicate ID '%(id)s' in feed '%(feed)s'"), {'id': id, 'feed': self}) self.implementations[id] = impl impl.metadata = item_attrs try: version_mod = item_attrs.get('version-modifier', None) if version_mod: item_attrs['version'] += version_mod del item_attrs['version-modifier'] version = item_attrs['version'] except KeyError: raise InvalidInterface(_("Missing version attribute")) impl.version = parse_version(version) impl.commands = commands impl.released = item_attrs.get('released', None) impl.langs = item_attrs.get('langs', '').replace('_', '-') size = item.getAttribute('size') if size: impl.size = int(size) impl.arch = item_attrs.get('arch', None) try: stability = stability_levels[str(item_attrs['stability'])] except KeyError: stab = str(item_attrs['stability']) if stab != stab.lower(): raise InvalidInterface(_('Stability "%s" invalid - use lower case!') % item_attrs.stability) raise InvalidInterface(_('Stability "%s" invalid') % item_attrs['stability']) if stability >= preferred: raise InvalidInterface(_("Upstream can't set stability to preferred!")) impl.upstream_stability = stability impl.bindings = bindings impl.requires = depends for elem in item.childNodes: if elem.uri != XMLNS_IFACE: continue if elem.name == 'archive': url = elem.getAttribute('href') if not url: raise InvalidInterface(_("Missing href attribute on <archive>")) size = elem.getAttribute('size') if not size: raise InvalidInterface(_("Missing size attribute on <archive>")) impl.add_download_source(url = url, size = int(size), extract = elem.getAttribute('extract'), start_offset = _get_long(elem, 'start-offset'), type = elem.getAttribute('type')) elif elem.name == 'manifest-digest': for aname, avalue in elem.attrs.items(): if ' ' not in aname: impl.digests.append(zerostore.format_algorithm_digest_pair(aname, avalue)) elif elem.name == 'recipe': recipe = Recipe() for recipe_step in elem.childNodes: if recipe_step.uri == XMLNS_IFACE and recipe_step.name == 'archive': url = recipe_step.getAttribute('href') if not url: raise InvalidInterface(_("Missing href attribute on <archive>")) size = recipe_step.getAttribute('size') if not size: raise InvalidInterface(_("Missing size attribute on <archive>")) recipe.steps.append(DownloadSource(None, url = url, size = int(size), extract = recipe_step.getAttribute('extract'), start_offset = _get_long(recipe_step, 'start-offset'), type = recipe_step.getAttribute('type'))) elif recipe_step.uri == XMLNS_IFACE and recipe_step.name == 'rename': source = recipe_step.getAttribute('source') if not source: raise InvalidInterface(_("Missing source attribute on <rename>")) dest = recipe_step.getAttribute('dest') if not dest: raise InvalidInterface(_("Missing dest attribute on <rename>")) recipe.steps.append(RenameStep(source=source, dest=dest)) else: logger.info(_("Unknown step '%s' in recipe; skipping recipe"), recipe_step.name) break else: impl.download_sources.append(recipe)
"""A quick DOM implementation. Python's xml.dom is very slow. The xml.sax module is also slow (as it imports urllib2). This is our light-weight version. """ # Copyright (C) 2009, Thomas Leonard # See the README file for details, or visit http://0install.net. from xml.parsers import expat import zeroinstall from zeroinstall.injector import versions _parsed_version = versions.parse_version(zeroinstall.version) class Element(object): """An XML element. @ivar uri: the element's namespace @type uri: str @ivar name: the element's localName @type name: str @ivar attrs: the element's attributes (key is in the form [namespace " "] localName) @type attrs: {str: str} @ivar childNodes: children @type childNodes: [L{Element}] @ivar content: the text content @type content: str""" __slots__ = ['uri', 'name', 'attrs', 'childNodes', 'content'] def __init__(self, uri, name, attrs): self.uri = uri
"""A quick DOM implementation. Python's xml.dom is very slow. The xml.sax module is also slow (as it imports urllib2). This is our light-weight version. """ # Copyright (C) 2009, Thomas Leonard # See the README file for details, or visit http://0install.net. from xml.parsers import expat import zeroinstall from zeroinstall.injector import versions _parsed_version = versions.parse_version(zeroinstall.version) class Element(object): """An XML element. @ivar uri: the element's namespace @type uri: str @ivar name: the element's localName @type name: str @ivar attrs: the element's attributes (key is in the form [namespace " "] localName) @type attrs: {str: str} @ivar childNodes: children @type childNodes: [L{Element}] @ivar content: the text content @type content: str""" __slots__ = ['uri', 'name', 'attrs', 'childNodes', 'content']