Start development series 0.25-post
[0publish.git] / validator.py
blob87155c4ff0f04b3c269bd6fcaf6ea4f1a7ed5f90
1 import os, io, StringIO
2 from zeroinstall.injector import model, namespaces, qdom
3 from zeroinstall.injector.reader import InvalidInterface, update
4 from xml.dom import minidom, Node, XMLNS_NAMESPACE
5 import tempfile
6 from logging import warn, info
8 group_impl_attribs = ['version', 'version-modifier', 'released', 'main', 'stability', 'arch', 'license', 'doc-dir', 'self-test', 'langs', 'local-path']
10 known_elements = {
11 'interface' : ['uri', 'min-injector-version', 'main'], # (main is deprecated)
12 'name' : [],
13 'summary' : [],
14 'description' : [],
15 'needs-terminal' : [],
16 'homepage' : [],
17 'category' : ['type'],
18 'icon' : ['type', 'href'],
19 'feed' : ['src', 'arch'],
20 'feed-for' : ['interface'],
21 'replaced-by' : ['interface'],
23 'group' : group_impl_attribs,
24 'implementation' : ['id'] + group_impl_attribs,
25 'package-implementation' : ['package', 'main', 'distributions'],
26 'manifest-digest' : ['sha1new', 'sha256', 'sha256new'],
27 'command' : ['name', 'path', 'shell-command'],
28 'arg' : [],
29 'for-each' : ['item-from', 'separator'],
31 'archive' : ['href', 'size', 'extract', 'type', 'start-offset'],
32 'recipe' : [],
33 'requires' : ['interface', 'use', 'importance', 'os', 'version'],
34 'restricts' : ['interface', 'os', 'version'],
35 'runner' : ['interface', 'use', 'importance', 'command'],
36 'version' : ['not-before', 'before'],
37 'environment' : ['name', 'insert', 'value', 'default', 'mode', 'separator'],
38 'executable-in-var' : ['name', 'command'],
39 'executable-in-path' : ['name', 'command'],
40 #'overlay' : ['src', 'mount-point'],
43 known_distros = frozenset([
44 "Windows", "Darwin", "Debian", "RPM", "Slack",
45 "Arch", "Gentoo", "Ports", "MacPorts", "Cygwin"
48 def checkElement(elem):
49 if elem.namespaceURI != namespaces.XMLNS_IFACE:
50 info("Note: Skipping unknown (but namespaced) element <%s>", elem.localName)
51 return # Namespaces elements are OK
53 if elem.localName not in known_elements:
54 warn("Unknown Zero Install element <%s>.\nNon Zero-Install elements should be namespaced.", elem.localName)
55 return
57 known_attrs = known_elements[elem.localName]
59 for (uri, name), value in elem.attributes.itemsNS():
60 if uri == XMLNS_NAMESPACE:
61 continue # Namespace declarations are fine
63 if name == 'if-0install-version':
64 try:
65 model.VersionExpressionRestriction(value) # Check syntax
66 except model.SafeException as ex:
67 warn("Bad if-0install-version expression: %s", ex)
68 continue
70 if uri:
71 info("Note: Skipping unknown (but namespaced) attribute '%s'", name)
72 continue
74 if name not in known_attrs:
75 warn("Unknown Zero Install attribute '%s' on <%s>.\nNon Zero-Install attributes should be namespaced.",
76 name, elem.localName)
78 if name == 'distributions':
79 if ',' in value:
80 warn("Use ' ' to separate distribution names, not ',' (%s)", value)
81 else:
82 for distro in value.split(' '):
83 if distro not in known_distros:
84 warn("Unknown distribution name '%s' (expected one of %s)", distro, '|'.join(known_distros))
86 for child in elem.childNodes:
87 if child.nodeType == Node.ELEMENT_NODE:
88 checkElement(child)
90 def check(data, warnings = True):
91 assert type(data) == bytes, type(data) # (must not be unicode)
93 try:
94 doc = minidom.parseString(data)
95 if doc.documentElement.getAttribute('uri'):
96 local_path = None
97 else:
98 local_path = '/tmp/local.xml'
99 model.ZeroInstallFeed(qdom.parse(StringIO.StringIO(data)), local_path = local_path)
100 except InvalidInterface, ex:
101 raise
102 except Exception, ex:
103 warn("Internal error: %s", ex)
104 raise InvalidInterface(str(ex))
106 if warnings:
107 checkElement(doc.documentElement)