3 # Copyright (C) 2015 Matthias Klumpp <mak@debian.org>
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 3.0 of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this program.
23 from voluptuous import Schema, Required, All, Any, Length, Range, Match, Url
24 from optparse import OptionParser
26 schema_header = Schema({
27 Required('File'): All(str, 'DEP-11', msg="Must be \"DEP-11\""),
28 Required('Origin'): All(str, Length(min=1)),
29 Required('Version'): All(str, Match(r'(\d+\.?)+$'), msg="Must be a valid version number"),
30 Required('MediaBaseUrl'): All(str, Url()),
31 'Time': All(str, str),
32 'Priority': All(str, int),
35 schema_translated = Schema({
36 Required('C'): All(str, Length(min=1), msg="Must have an unlocalized 'C' key"),
37 dict: All(str, Length(min=1)),
40 schema_component = Schema({
41 Required('Type'): All(str, Length(min=1)),
42 Required('ID'): All(str, Length(min=1)),
43 Required('Name'): All(dict, Length(min=1), schema_translated),
44 Required('Package'): All(str, Length(min=1)),
50 def test_custom_objects(lines):
52 for i in range(0, len(lines)):
53 if "!!python/" in lines[i]:
54 add_issue("Python object encoded in line %i." % (i))
59 return (s.startswith("\"") and s.endswith("\"")) or (s.startswith("\'") and s.endswith("\'"))
61 def test_localized_dict(doc, ldict, id_string):
63 for lang, value in ldict.items():
65 add_issue("[%s][%s]: %s" % (doc['ID'], id_string, "Found cruft locale: x-test"))
67 add_issue("[%s][%s]: %s" % (doc['ID'], id_string, "Found cruft locale: xx"))
68 if lang.endswith('.UTF-8'):
69 add_issue("[%s][%s]: %s" % (doc['ID'], id_string, "AppStream locale names should not specify encoding (ends with .UTF-8)"))
71 add_issue("[%s][%s]: %s" % (doc['ID'], id_string, "String is quoted: '%s' @ %s" % (value, lang)))
73 add_issue("[%s][%s]: %s" % (doc['ID'], id_string, "Locale name contains space: '%s'" % (lang)))
74 # this - as opposed to the other issues - is an error
78 def test_localized(doc, key):
79 ldict = doc.get(key, None)
83 return test_localized_dict(doc, ldict, key)
85 def validate_data(data):
87 lines = data.split("\n")
89 # see if there are any Python-specific objects encoded
90 ret = test_custom_objects(lines)
93 docs = yaml.safe_load_all(data)
95 except Exception as e:
96 add_issue("Could not parse file: %s" % (str(e)))
100 schema_header(header)
101 except Exception as e:
102 add_issue("Invalid DEP-11 header: %s" % (str(e)))
106 docid = doc.get('ID')
107 pkgname = doc.get('Package')
109 pkgname = "?unknown?"
111 add_issue("FATAL: Empty document found.")
115 add_issue("FATAL: Component without ID found.")
120 schema_component(doc)
121 except Exception as e:
122 add_issue("[%s]: %s" % (docid, str(e)))
126 # more tests for the icon key
127 icon = doc.get('Icon')
128 if (doc['Type'] == "desktop-app") or (doc['Type'] == "web-app"):
129 if not doc.get('Icon'):
130 add_issue("[%s]: %s" % (docid, "Components containing an application must have an 'Icon' key."))
133 if (not icon.get('stock')) and (not icon.get('cached')) and (not icon.get('local')):
134 add_issue("[%s]: %s" % (docid, "A 'stock', 'cached' or 'local' icon must at least be provided. @ data['Icon']"))
137 if not test_localized(doc, 'Name'):
139 if not test_localized(doc, 'Summary'):
141 if not test_localized(doc, 'Description'):
143 if not test_localized(doc, 'DeveloperName'):
146 for shot in doc.get('Screenshots', list()):
147 caption = shot.get('caption')
149 if not test_localized_dict(doc, caption, "Screenshots.x.caption"):
154 def validate_file(fname):
156 if fname.endswith(".gz"):
157 f = gzip.open(fname, 'r')
158 elif fname.endswith(".xz"):
159 f = lzma.open(fname, 'r')
163 data = str(f.read(), 'utf-8')
166 return validate_data(data)
168 def validate_dir(dirname):
170 for root, subfolders, files in os.walk(dirname):
172 if fname.endswith(".yml.gz") or fname.endswith(".yml.xz"):
173 if not validate_file(os.path.join(root, fname)):
179 parser = OptionParser()
181 (options, args) = parser.parse_args()
184 print("You need to specify a file to validate!")
188 if os.path.isdir(fname):
189 ret = validate_dir(fname)
191 ret = validate_file(fname)
193 msg = "DEP-11 basic validation successful."
195 msg = "DEP-11 validation failed!"
201 if __name__ == "__main__":