1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 """
38 Internal library for processing 'manifest' files, i.e. manifest.xml and stack.xml.
39 For external code apis, see L{roslib.manifest} and L{roslib.stack_manifest}.
40 """
41
42 import sys
43 import os
44 import xml.dom
45 import xml.dom.minidom as dom
46
47 import roslib.exceptions
48
49
50 REQUIRED = ['author', 'license']
51 ALLOWXHTML = ['description']
52 OPTIONAL = ['logo', 'url', 'brief', 'description', 'status',
53 'notes', 'depend', 'rosdep', 'export', 'review',
54 'versioncontrol', 'platform', 'version', 'rosbuild2']
55 VALID = REQUIRED + OPTIONAL
56
58
60 return [t for t in n.childNodes if t.nodeType == t.ELEMENT_NODE and t.tagName == name]
61
63 """
64 Validator for optional elements.
65 @raise ManifestException: if validation fails
66 """
67 def check(n, filename):
68 n = get_nodes_by_name(n, name)
69 if len(n) > 1:
70 raise ManifestException("Invalid manifest file: must have a single '%s' element"%name)
71 if n:
72 if allowXHTML:
73 return ''.join([x.toxml() for x in n[0].childNodes])
74 return _get_text(n[0].childNodes).strip()
75 return check
76
78 """
79 Validator for required elements.
80 @raise ManifestException: if validation fails
81 """
82 def check(n, filename):
83 n = get_nodes_by_name(n, name)
84 if not n:
85
86 return ''
87 if len(n) != 1:
88 raise ManifestException("Invalid manifest file: must have only one '%s' element"%name)
89 if allowXHTML:
90 return ''.join([x.toxml() for x in n[0].childNodes])
91 return _get_text(n[0].childNodes).strip()
92 return check
93
106 return check
107
109 """
110 Validator for manifest depends.
111 @raise ManifestException: if validation fails
112 """
113 def check(n, filename):
114 nodes = get_nodes_by_name(n, name)
115
116
117
118
119
120 depends = [e.attributes for e in nodes if 'thirdparty' not in e.attributes.keys()]
121 try:
122 packages = [d['package'].value for d in depends]
123 except KeyError:
124 raise ManifestException("Invalid manifest file: depends is missing 'package' attribute")
125
126 return [Depend(p) for p in packages]
127 return check
128
139 return check
140
142 """
143 Validator for stack rosdeps.
144 @raise ManifestException: if validation fails
145 """
146 def check(n, filename):
147 nodes = get_nodes_by_name(n, name)
148 rosdeps = [e.attributes for e in nodes]
149 names = [d['name'].value for d in rosdeps]
150 return [ROSDep(n) for n in names]
151 return check
152
154 attrs = {}
155 for k in node.attributes.keys():
156 attrs[k] = node.attributes.get(k).value
157 return attrs
158
160 def check(n, filename):
161 ret_val = []
162 for e in get_nodes_by_name(n, name):
163 elements = [c for c in e.childNodes if c.nodeType == c.ELEMENT_NODE]
164 ret_val.extend([Export(t.tagName, _attrs(t), _get_text(t.childNodes)) for t in elements])
165 return ret_val
166 return check
167
169 def check(n, filename):
170 e = get_nodes_by_name(n, name)
171 if not e:
172 return None
173
174 return VersionControl(e[0].attributes['type'].value, e[0].attributes['url'].value)
175 return check
176
196
198 """
199 Manifest 'export' tag
200 """
201
203 """
204 Create new export instance.
205 @param tag: name of the XML tag
206 @type tag: str
207 @param attrs: dictionary of XML attributes for this export tag
208 @type attrs: dict
209 @param str: string value contained by tag, if any
210 @type str: str
211 """
212 self.tag = tag
213 self.attrs = attrs
214 self.str = str
215
216 - def get(self, attr):
217 """
218 @return: value of attribute or None if attribute not set
219 @rtype: str
220 """
221 return self.attrs.get(attr, None)
223 """
224 @return: export instance represented as manifest XML
225 @rtype: str
226 """
227 attrs = ' '.join([' %s="%s"'%(k,v) for k,v in self.attrs.items()])
228 if self.str:
229 return '<%s%s>%s</%s>'%(self.tag, attrs, self.str, self.tag)
230 else:
231 return '<%s%s />'%(self.tag, attrs)
232
277
279 """
280 Manifest 'depend' tag
281 """
282 __slots__ = ['package']
283
285 """
286 Create new depend instance.
287 @param package: package name. must be non-empty
288 @type package: str
289 """
290 if not package:
291 raise ValueError("bad 'package' attribute")
292 self.package = package
302 """
303 @return: depend instance represented as manifest XML
304 @rtype: str
305 """
306 return '<depend package="%s" />'%self.package
307
309 """
310 Stack Manifest 'depend' tag
311 """
312 __slots__ = ['stack', 'annotation']
313
315 """
316 @param stack: stack name. must be non-empty
317 @type stack: str
318 """
319 if not stack:
320 raise ValueError("bad 'stack' attribute")
321 self.stack = stack
322 self.annotation = None
323
333 """
334 @return: stack depend instance represented as stack manifest XML
335 @rtype: str
336 """
337 if self.annotation:
338 return '<depend stack="%s" /> <!-- %s -->'%(self.stack, self.annotation)
339 else:
340 return '<depend stack="%s" />'%self.stack
341
343 """
344 Manifest 'rosdep' tag
345 """
346 __slots__ = ['name',]
347
349 """
350 Create new rosdep instance.
351 @param name: dependency name. Must be non-empty.
352 @type name: str
353 """
354 if not name:
355 raise ValueError("bad 'name' attribute")
356 self.name = name
358 """
359 @return: rosdep instance represented as manifest XML
360 @rtype: str
361 """
362 return '<rosdep name="%s" />'%self.name
363
365 """
366 Manifest 'versioncontrol' tag
367 """
368 __slots__ = ['type', 'url']
369
371 """
372 @param type_: version control type (e.g. 'svn'). must be non empty
373 @type type_: str
374 @param url: URL associated with version control. must be non empty
375 @type url: str
376 """
377 if not type_ or not isinstance(type_, basestring):
378 raise ValueError("bad 'type' attribute")
379 if not url is None and not isinstance(url, basestring):
380 raise ValueError("bad 'url' attribute")
381 self.type = type_
382 self.url = url
384 """
385 @return: versioncontrol instance represented as manifest XML
386 @rtype: str
387 """
388 if self.url:
389 return '<versioncontrol type="%s" url="%s" />'%(self.type, self.url)
390 else:
391 return '<versioncontrol type="%s" />'%self.type
392
394 """
395 Object representation of a ROS manifest file
396 """
397 __slots__ = ['description', 'brief', \
398 'author', 'license', 'license_url', 'url', \
399 'depends', 'rosdeps','platforms',\
400 'logo', 'exports', 'version',\
401 'versioncontrol', 'status', 'notes',\
402 'unknown_tags',\
403 '_type']
405 self.description = self.brief = self.author = \
406 self.license = self.license_url = \
407 self.url = self.logo = self.status = \
408 self.version = self.notes = ''
409 self.depends = []
410 self.rosdeps = []
411 self.exports = []
412 self.platforms = []
413 self._type = _type
414
415
416 self.unknown_tags = []
417
421 """
422 @return: exports that match the specified tag and attribute, e.g. 'python', 'path'
423 @rtype: [L{Export}]
424 """
425 return [e.get(attr) for e in self.exports if e.tag == tag if e.get(attr) is not None]
427 """
428 @return: Manifest instance as ROS XML manifest
429 @rtype: str
430 """
431 if not self.brief:
432 desc = " <description>%s</description>"%self.description
433 else:
434 desc = ' <description brief="%s">%s</description>'%(self.brief, self.description)
435 author = " <author>%s</author>"%self.author
436 if self.license_url:
437 license = ' <license url="%s">%s</license>'%(self.license_url, self.license)
438 else:
439 license = " <license>%s</license>"%self.license
440 versioncontrol = url = logo = exports = version = ""
441 if self.url:
442 url = " <url>%s</url>"%self.url
443 if self.version:
444 version = " <version>%s</version>"%self.version
445 if self.logo:
446 logo = " <logo>%s</logo>"%self.logo
447 depends = '\n'.join([" %s"%d.xml() for d in self.depends])
448 rosdeps = '\n'.join([" %s"%rd.xml() for rd in self.rosdeps])
449 platforms = '\n'.join([" %s"%p.xml() for p in self.platforms])
450 if self.exports:
451 exports = ' <export>\n' + '\n'.join([" %s"%e.xml() for e in self.exports]) + ' </export>'
452 if self.versioncontrol:
453 versioncontrol = " %s"%self.versioncontrol.xml()
454 if self.status or self.notes:
455 review = ' <review status="%s" notes="%s" />'%(self.status, self.notes)
456
457
458 fields = filter(lambda x: x,
459 [desc, author, license, review, url, logo, depends,
460 rosdeps, platforms, exports, versioncontrol, version])
461 return "<%s>\n"%self._type + "\n".join(fields) + "\n</%s>"%self._type
462
463 -def _get_text(nodes):
464 """
465 DOM utility routine for getting contents of text nodes
466 """
467 return "".join([n.data for n in nodes if n.nodeType == n.TEXT_NODE])
468
470 """
471 Parse manifest file (package, stack)
472 @param m: field to populate
473 @type m: L{_Manifest}
474 @param file: manifest.xml file path
475 @type file: str
476 @return: return m, populated with parsed fields
477 @rtype: L{_Manifest}
478 """
479 if not file:
480 raise ValueError("Missing manifest file argument")
481 if not os.path.isfile(file):
482 raise ValueError("Invalid/non-existent manifest file: %s"%file)
483 with open(file, 'r') as f:
484 text = f.read()
485 try:
486 return parse(m, text, file)
487 except ManifestException as e:
488 raise ManifestException("Invalid manifest file [%s]: %s"%(os.path.abspath(file), e))
489
490 -def parse(m, string, filename='string'):
491 """
492 Parse manifest.xml string contents
493 @param string: manifest.xml contents
494 @type string: str
495 @param m: field to populate
496 @type m: L{_Manifest}
497 @return: return m, populated with parsed fields
498 @rtype: L{_Manifest}
499 """
500 try:
501 d = dom.parseString(string)
502 except Exception as e:
503 raise ManifestException("invalid XML: %s"%e)
504
505 p = get_nodes_by_name(d, m._type)
506 if len(p) != 1:
507 raise ManifestException("manifest must have a single '%s' element"%m._type)
508 p = p[0]
509 m.description = check('description')(p, filename)
510 m.brief = ''
511 try:
512 tag = get_nodes_by_name(p, 'description')[0]
513 m.brief = tag.getAttribute('brief') or ''
514 except:
515
516 pass
517
518 if m._type == 'package':
519 m.depends = check_depends('depend')(p, filename)
520 elif m._type == 'stack':
521 m.depends = check_stack_depends('depend')(p, filename)
522 elif m._type == 'app':
523
524 pass
525 m.rosdeps = check('rosdep')(p, filename)
526 m.platforms = check('platform')(p, filename)
527 m.exports = check('export')(p, filename)
528 m.versioncontrol = check('versioncontrol')(p,filename)
529 m.license = check('license')(p, filename)
530 m.license_url = ''
531 try:
532 tag = get_nodes_by_name(p, 'license')[0]
533 m.license_url = tag.getAttribute('url') or ''
534 except:
535 pass
536
537 m.status='unreviewed'
538 try:
539 tag = get_nodes_by_name(p, 'review')[0]
540 m.status=tag.getAttribute('status') or ''
541 except:
542 pass
543
544 m.notes=''
545 try:
546 tag = get_nodes_by_name(p, 'review')[0]
547 m.notes=tag.getAttribute('notes') or ''
548 except:
549 pass
550
551 m.author = check('author')(p, filename)
552 m.url = check('url')(p, filename)
553 m.version = check('version')(p, filename)
554 m.logo = check('logo')(p, filename)
555
556
557 if m._type == 'stack':
558 if m.exports:
559 raise ManifestException("stack manifests are not allowed to have exports")
560 if m.rosdeps:
561 raise ManifestException("stack manifests are not allowed to have rosdeps")
562
563
564 m.unknown_tags = [e for e in p.childNodes if e.nodeType == e.ELEMENT_NODE and e.tagName not in VALID]
565 return m
566