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 os
43 import xml.dom.minidom as dom
44
45 import roslib.exceptions
46
47
48 REQUIRED = ['author', 'license']
49 ALLOWXHTML = ['description']
50 OPTIONAL = ['logo', 'url', 'brief', 'description', 'status',
51 'notes', 'depend', 'rosdep', 'export', 'review',
52 'versioncontrol', 'platform', 'version', 'rosbuild2',
53 'catkin']
54 VALID = REQUIRED + OPTIONAL
55
56
59
60
62 return [t for t in n.childNodes if t.nodeType == t.ELEMENT_NODE and t.tagName == name]
63
64
66 """
67 Validator for optional elements.
68 @raise ManifestException: if validation fails
69 """
70 def check(n, filename):
71 n = get_nodes_by_name(n, name)
72 if len(n) > 1 and not merge_multiple:
73 raise ManifestException("Invalid manifest file: must have a single '%s' element" % name)
74 if n:
75 values = []
76 for child in n:
77 if allowXHTML:
78 values.append(''.join([x.toxml() for x in child.childNodes]))
79 else:
80 values.append(_get_text(child.childNodes).strip())
81 return ', '.join(values)
82 return check
83
84
86 """
87 Validator for required elements.
88 @raise ManifestException: if validation fails
89 """
90 def check(n, filename):
91 n = get_nodes_by_name(n, name)
92 if not n:
93
94 return ''
95 if len(n) != 1 and not merge_multiple:
96 raise ManifestException("Invalid manifest file: must have only one '%s' element" % name)
97 values = []
98 for child in n:
99 if allowXHTML:
100 values.append(''.join([x.toxml() for x in child.childNodes]))
101 else:
102 values.append(_get_text(child.childNodes).strip())
103 return ', '.join(values)
104 return check
105
106
119 return check
120
121
123 """
124 Validator for manifest depends.
125 @raise ManifestException: if validation fails
126 """
127 def check(n, filename):
128 nodes = get_nodes_by_name(n, name)
129
130
131
132
133
134 depends = [e.attributes for e in nodes if 'thirdparty' not in e.attributes.keys()]
135 try:
136 packages = [d['package'].value for d in depends]
137 except KeyError:
138 raise ManifestException("Invalid manifest file: depends is missing 'package' attribute")
139
140 return [Depend(p) for p in packages]
141 return check
142
143
145 """
146 Validator for stack depends.
147 @raise ManifestException: if validation fails
148 """
149 def check(n, filename):
150 nodes = get_nodes_by_name(n, name)
151 depends = [e.attributes for e in nodes]
152 packages = [d['stack'].value for d in depends]
153 return [StackDepend(p) for p in packages]
154 return check
155
156
158 """
159 Validator for stack rosdeps.
160 @raise ManifestException: if validation fails
161 """
162 def check(n, filename):
163 nodes = get_nodes_by_name(n, name)
164 rosdeps = [e.attributes for e in nodes]
165 names = [d['name'].value for d in rosdeps]
166 return [ROSDep(n) for n in names]
167 return check
168
169
171 attrs = {}
172 for k in node.attributes.keys():
173 attrs[k] = node.attributes.get(k).value
174 return attrs
175
176
178 def check(n, filename):
179 ret_val = []
180 for e in get_nodes_by_name(n, name):
181 elements = [c for c in e.childNodes if c.nodeType == c.ELEMENT_NODE]
182 ret_val.extend([Export(t.tagName, _attrs(t), _get_text(t.childNodes)) for t in elements])
183 return ret_val
184 return check
185
186
188 def check(n, filename):
189 e = get_nodes_by_name(n, name)
190 if not e:
191 return None
192
193 return VersionControl(e[0].attributes['type'].value, e[0].attributes['url'].value)
194 return check
195
196
197 -def check(name, merge_multiple=False):
216
217
219 """
220 Manifest 'export' tag
221 """
222
224 """
225 Create new export instance.
226 @param tag: name of the XML tag
227 @type tag: str
228 @param attrs: dictionary of XML attributes for this export tag
229 @type attrs: dict
230 @param str: string value contained by tag, if any
231 @type str: str
232 """
233 self.tag = tag
234 self.attrs = attrs
235 self.str = str
236
237 - def get(self, attr):
238 """
239 @return: value of attribute or None if attribute not set
240 @rtype: str
241 """
242 return self.attrs.get(attr, None)
243
245 """
246 @return: export instance represented as manifest XML
247 @rtype: str
248 """
249 attrs = ' '.join([' %s="%s"' % (k, v) for k, v in self.attrs.items()])
250 if self.str:
251 return '<%s%s>%s</%s>' % (self.tag, attrs, self.str, self.tag)
252 else:
253 return '<%s%s />' % (self.tag, attrs)
254
255
303
304
306 """
307 Manifest 'depend' tag
308 """
309 __slots__ = ['package']
310
312 """
313 Create new depend instance.
314 @param package: package name. must be non-empty
315 @type package: str
316 """
317 if not package:
318 raise ValueError("bad 'package' attribute")
319 self.package = package
320
323
326
331
333 """
334 @return: depend instance represented as manifest XML
335 @rtype: str
336 """
337 return '<depend package="%s" />' % self.package
338
339
341 """
342 Stack Manifest 'depend' tag
343 """
344 __slots__ = ['stack', 'annotation']
345
347 """
348 @param stack: stack name. must be non-empty
349 @type stack: str
350 """
351 if not stack:
352 raise ValueError("bad 'stack' attribute")
353 self.stack = stack
354 self.annotation = None
355
358
361
366
368 """
369 @return: stack depend instance represented as stack manifest XML
370 @rtype: str
371 """
372 if self.annotation:
373 return '<depend stack="%s" /> <!-- %s -->' % (self.stack, self.annotation)
374 else:
375 return '<depend stack="%s" />' % self.stack
376
377
379 """
380 Manifest 'rosdep' tag
381 """
382 __slots__ = ['name', ]
383
385 """
386 Create new rosdep instance.
387 @param name: dependency name. Must be non-empty.
388 @type name: str
389 """
390 if not name:
391 raise ValueError("bad 'name' attribute")
392 self.name = name
393
395 """
396 @return: rosdep instance represented as manifest XML
397 @rtype: str
398 """
399 return '<rosdep name="%s" />' % self.name
400
401
403 """
404 Manifest 'versioncontrol' tag
405 """
406 __slots__ = ['type', 'url']
407
409 """
410 @param type_: version control type (e.g. 'svn'). must be non empty
411 @type type_: str
412 @param url: URL associated with version control. must be non empty
413 @type url: str
414 """
415 def is_string_type(obj):
416 try:
417 return isinstance(obj, basestring)
418 except NameError:
419 return isinstance(obj, str)
420
421 if not type_ or not is_string_type(type_):
422 raise ValueError("bad 'type' attribute")
423 if url is not None and not is_string_type(url):
424 raise ValueError("bad 'url' attribute")
425 self.type = type_
426 self.url = url
427
429 """
430 @return: versioncontrol instance represented as manifest XML
431 @rtype: str
432 """
433 if self.url:
434 return '<versioncontrol type="%s" url="%s" />' % (self.type, self.url)
435 else:
436 return '<versioncontrol type="%s" />' % self.type
437
438
440 """
441 Object representation of a ROS manifest file
442 """
443 __slots__ = ['description', 'brief',
444 'author', 'license', 'license_url', 'url',
445 'depends', 'rosdeps', 'platforms',
446 'logo', 'exports', 'version',
447 'versioncontrol', 'status', 'notes',
448 'unknown_tags',
449 '_type']
450
452 self.description = self.brief = self.author = \
453 self.license = self.license_url = \
454 self.url = self.logo = self.status = \
455 self.version = self.notes = ''
456 self.depends = []
457 self.rosdeps = []
458 self.exports = []
459 self.platforms = []
460 self._type = _type
461
462
463 self.unknown_tags = []
464
467
469 """
470 @return: exports that match the specified tag and attribute, e.g. 'python', 'path'
471 @rtype: [L{Export}]
472 """
473 return [e.get(attr) for e in self.exports if e.tag == tag if e.get(attr) is not None]
474
476 """
477 @return: Manifest instance as ROS XML manifest
478 @rtype: str
479 """
480 if not self.brief:
481 desc = ' <description>%s</description>' % self.description
482 else:
483 desc = ' <description brief="%s">%s</description>' % (self.brief, self.description)
484 author = ' <author>%s</author>' % self.author
485 if self.license_url:
486 license = ' <license url="%s">%s</license>' % (self.license_url, self.license)
487 else:
488 license = ' <license>%s</license>' % self.license
489 versioncontrol = url = logo = exports = version = ''
490 if self.url:
491 url = ' <url>%s</url>' % self.url
492 if self.version:
493 version = ' <version>%s</version>' % self.version
494 if self.logo:
495 logo = ' <logo>%s</logo>' % self.logo
496 depends = '\n'.join([' %s' % d.xml() for d in self.depends])
497 rosdeps = '\n'.join([' %s' % rd.xml() for rd in self.rosdeps])
498 platforms = '\n'.join([' %s' % p.xml() for p in self.platforms])
499 if self.exports:
500 exports = ' <export>\n' + '\n'.join([' %s' % e.xml() for e in self.exports]) + ' </export>'
501 if self.versioncontrol:
502 versioncontrol = ' %s' % self.versioncontrol.xml()
503 if self.status or self.notes:
504 review = ' <review status="%s" notes="%s" />' % (self.status, self.notes)
505
506 fields = filter(lambda x: x,
507 [desc, author, license, review, url, logo, depends,
508 rosdeps, platforms, exports, versioncontrol, version])
509 return '<%s>\n' % self._type + '\n'.join(fields) + '\n</%s>' % self._type
510
511
512 -def _get_text(nodes):
513 """
514 DOM utility routine for getting contents of text nodes
515 """
516 return ''.join([n.data for n in nodes if n.nodeType == n.TEXT_NODE])
517
518
520 """
521 Parse manifest file (package, stack)
522 @param m: field to populate
523 @type m: L{_Manifest}
524 @param file: manifest.xml file path
525 @type file: str
526 @return: return m, populated with parsed fields
527 @rtype: L{_Manifest}
528 """
529 if not file:
530 raise ValueError('Missing manifest file argument')
531 if not os.path.isfile(file):
532 raise ValueError('Invalid/non-existent manifest file: %s' % file)
533 with open(file, 'r') as f:
534 text = f.read()
535 try:
536 return parse(m, text, file)
537 except ManifestException as e:
538 raise ManifestException('Invalid manifest file [%s]: %s' % (os.path.abspath(file), e))
539
540
541 -def parse(m, string, filename='string'):
542 """
543 Parse manifest.xml string contents
544 @param string: manifest.xml contents
545 @type string: str
546 @param m: field to populate
547 @type m: L{_Manifest}
548 @return: return m, populated with parsed fields
549 @rtype: L{_Manifest}
550 """
551 try:
552 d = dom.parseString(string)
553 except Exception as e:
554 raise ManifestException('invalid XML: %s' % e)
555
556 p = get_nodes_by_name(d, m._type)
557 if len(p) != 1:
558 raise ManifestException("manifest must have a single '%s' element" % m._type)
559 p = p[0]
560 m.description = check('description')(p, filename)
561 m.brief = ''
562 try:
563 tag = get_nodes_by_name(p, 'description')[0]
564 m.brief = tag.getAttribute('brief') or ''
565 except Exception:
566
567 pass
568
569 if m._type == 'package':
570 m.depends = check_depends('depend')(p, filename)
571 elif m._type == 'stack':
572 m.depends = check_stack_depends('depend')(p, filename)
573 elif m._type == 'app':
574
575 pass
576 m.rosdeps = check('rosdep')(p, filename)
577 m.platforms = check('platform')(p, filename)
578 m.exports = check('export')(p, filename)
579 m.versioncontrol = check('versioncontrol')(p, filename)
580 m.license = check('license')(p, filename)
581 m.license_url = ''
582 try:
583 tag = get_nodes_by_name(p, 'license')[0]
584 m.license_url = tag.getAttribute('url') or ''
585 except Exception:
586 pass
587
588 m.status = 'unreviewed'
589 try:
590 tag = get_nodes_by_name(p, 'review')[0]
591 m.status = tag.getAttribute('status') or ''
592 except Exception:
593 pass
594
595 m.notes = ''
596 try:
597 tag = get_nodes_by_name(p, 'review')[0]
598 m.notes = tag.getAttribute('notes') or ''
599 except Exception:
600 pass
601
602 m.author = check('author', True)(p, filename)
603 m.url = check('url')(p, filename)
604 m.version = check('version')(p, filename)
605 m.logo = check('logo')(p, filename)
606
607
608 if m._type == 'stack':
609 if m.exports:
610 raise ManifestException('stack manifests are not allowed to have exports')
611 if m.rosdeps:
612 raise ManifestException('stack manifests are not allowed to have rosdeps')
613
614
615 m.unknown_tags = [e for e in p.childNodes if e.nodeType == e.ELEMENT_NODE and e.tagName not in VALID]
616 return m
617