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 General routines and representations for loading roslaunch model.
37 """
38
39 from __future__ import with_statement
40
41 import os
42 import sys
43 from copy import deepcopy
44
45 from roslaunch.core import Param, RosbinExecutable, Node, Test, Machine, \
46 RLException, PHASE_SETUP
47
48 try:
49 from roslib.names import make_global_ns, ns_join, PRIV_NAME, load_mappings, is_legal_name, canonicalize_name
50 except ImportError:
51 raise ImportError('Cannot import new roslib libraries.\nThis is probably due to incompatible "roslib" directories on your PYTHONPATH.\nPlease check your PYTHONPATH and try again')
52
53
54
55 yaml = None
56
58 """Error loading data as specified (e.g. cannot find included files, etc...)"""
59 pass
60
61
63 """
64 Convert a value from a string representation into the specified
65 type
66
67 @param value: string representation of value
68 @type value: str
69 @param type_: int, double, string, bool, or auto
70 @type type_: str
71 @raise ValueError: if parameters are invalid
72 """
73 type_ = type_.lower()
74
75
76 if type_ == 'auto':
77
78 try:
79 if '.' in value:
80 return float(value)
81 else:
82 return int(value)
83 except ValueError, e:
84 pass
85
86 lval = value.lower()
87 if lval == 'true' or lval == 'false':
88 return convert_value(value, 'bool')
89
90 return value
91 elif type_ == 'str' or type_ == 'string':
92 return value
93 elif type_ == 'int':
94 return int(value)
95 elif type_ == 'double':
96 return float(value)
97 elif type_ == 'bool' or type_ == 'boolean':
98 value = value.lower()
99 if value == 'true' or value == '1':
100 return True
101 elif value == 'false' or value == '0':
102 return False
103 raise ValueError("%s is not a '%s' type"%(value, type_))
104 else:
105 raise ValueError("Unknown type '%s'"%type_)
106
108 """
109 Processes arg declarations in context and makes sure that they are
110 properly declared for passing into an included file. Also will
111 correctly setup the context for passing to the included file.
112 """
113
114
115
116 arg_dict = context.include_resolve_dict.get('arg', {})
117 for arg in context.arg_names:
118 if not arg in arg_dict:
119 raise LoadException("include args must have declared values")
120
121
122 context.args_passed = arg_dict.keys()[:]
123
124 context.arg_names = []
125
126
127 context.resolve_dict = context.include_resolve_dict
128 context.include_resolve_dict = None
129
131 bad = [a for a in context.args_passed if a not in context.arg_names]
132 if bad:
133 raise LoadException("unused args [%s] for include of [%s]"%(', '.join(bad), context.filename))
134
136 """
137 Load in ROS remapping arguments as arg assignments for context.
138
139 @param context: context to load into. context's resolve_dict for 'arg' will be reinitialized with values.
140 @type context: L{LoaderContext{
141 @param argv: command-line arguments
142 @type argv: [str]
143 """
144
145 mappings = load_mappings(argv)
146 context.resolve_dict['arg'] = mappings
147
148 -class LoaderContext(object):
149 """
150 Container for storing current loader context (e.g. namespace,
151 local parameter state, remapping state).
152 """
153
154 - def __init__(self, ns, filename, parent=None, params=None, env_args=None, \
155 resolve_dict=None, include_resolve_dict=None, arg_names=None):
156 """
157 @param ns: namespace
158 @type ns: str
159 @param filename: name of file this is being loaded from
160 @type filename: str
161 @param resolve_dict: (optional) resolution dictionary for substitution args
162 @type resolve_dict: dict
163 @param include_resolve_dict: special resolution dictionary for
164 <include> tags. Must be None if this is not an <include>
165 context.
166 @type include_resolve_dict: dict
167 @param arg_names: name of args that have been declared in this context
168 @type arg_names: [str]
169 """
170 self.parent = parent
171 self.ns = make_global_ns(ns or '/')
172 self._remap_args = []
173 self.params = params or []
174 self.env_args = env_args or []
175 self.filename = filename
176
177 self.resolve_dict = resolve_dict or {}
178
179 self.arg_names = arg_names or []
180
181 self.include_resolve_dict = include_resolve_dict or None
182
183 - def add_param(self, p):
184 """
185 Add a ~param to the context. ~params are evaluated by any node
186 declarations that occur later in the same context.
187
188 @param p: parameter
189 @type p: L{Param}
190 """
191
192
193 matches = [m for m in self.params if m.key == p.key]
194 for m in matches:
195 self.params.remove(m)
196 self.params.append(p)
197
198 - def add_remap(self, remap):
199 """
200 Add a new remap setting to the context. if a remap already
201 exists with the same from key, it will be removed
202
203 @param remap: remap setting
204 @type remap: (str, str)
205 """
206 remap = [canonicalize_name(x) for x in remap]
207 if not remap[0] or not remap[1]:
208 raise RLException("remap from/to attributes cannot be empty")
209 if not is_legal_name(remap[0]):
210 raise RLException("remap from [%s] is not a valid ROS name"%remap[0])
211 if not is_legal_name(remap[1]):
212 raise RLException("remap to [%s] is not a valid ROS name"%remap[1])
213
214 matches = [r for r in self._remap_args if r[0] == remap[0]]
215 for m in matches:
216 self._remap_args.remove(m)
217 self._remap_args.append(remap)
218
219 - def add_arg(self, name, default=None, value=None):
220 """
221 Add 'arg' to existing context. Args are only valid for their immediate context.
222 """
223 if name in self.arg_names:
224 raise LoadException("arg '%s' has already been declared"%name)
225 self.arg_names.append(name)
226
227 resolve_dict = self.resolve_dict if self.include_resolve_dict is None else self.include_resolve_dict
228
229 if not 'arg' in resolve_dict:
230 resolve_dict['arg'] = {}
231 arg_dict = resolve_dict['arg']
232
233
234
235 if value is not None:
236
237
238 if name in arg_dict:
239 raise LoadException("cannot override arg '%s', which has already been set"%name)
240 arg_dict[name] = value
241 elif default is not None:
242
243 if name not in arg_dict:
244 arg_dict[name] = default
245 else:
246
247
248 pass
249
250 - def remap_args(self):
251 """
252 @return: copy of the current remap arguments
253 @rtype: [(str, str)]
254 """
255 if self.parent:
256 args = []
257
258 for pr in self.parent.remap_args():
259 if not [r for r in self._remap_args if r[0] == pr[0]]:
260 args.append(pr)
261 args.extend(self._remap_args)
262 return args
263 return self._remap_args[:]
264
265 - def include_child(self, ns, filename):
266 """
267 Create child namespace based on include inheritance rules
268 @param ns: sub-namespace of child context, or None if the
269 child context shares the same namespace
270 @type ns: str
271 @param filename: name of include file
272 @type filename: str
273 @return: A child xml context that inherits from this context
274 @rtype: L{LoaderContext}jj
275 """
276 ctx = self.child(ns)
277
278 ctx.arg_names = []
279 ctx.filename = filename
280
281 ctx.include_resolve_dict = {}
282
283 return ctx
284
285 - def child(self, ns):
286 """
287 @param ns: sub-namespace of child context, or None if the
288 child context shares the same namespace
289 @type ns: str
290 @return: A child xml context that inherits from this context
291 @rtype: L{LoaderContext}
292 """
293 if ns:
294 if ns[0] == '/':
295 child_ns = ns
296 elif ns == PRIV_NAME:
297
298 child_ns = PRIV_NAME
299 else:
300 child_ns = ns_join(self.ns, ns)
301 else:
302 child_ns = self.ns
303 return LoaderContext(child_ns, self.filename, parent=self,
304 params=self.params, env_args=self.env_args[:],
305 resolve_dict=deepcopy(self.resolve_dict),
306 arg_names=self.arg_names[:], include_resolve_dict=self.include_resolve_dict)
307
308
309
310
311
312
314 """
315 Lower-level library for loading ROS launch model. It provides an
316 abstraction between the representation (e.g. XML) and the
317 validation of the property values.
318 """
319
320 - def add_param(self, ros_config, param_name, param_value, verbose=True):
321 """
322 Add L{Param} instances to launch config. Dictionary values are
323 unrolled into individual parameters.
324
325 @param ros_config: launch configuration
326 @type ros_config: L{ROSLaunchConfig}
327 @param param_name: name of parameter namespace to load values
328 into. If param_name is '/', param_value must be a dictionary
329 @type param_name: str
330 @param param_value: value to assign to param_name. If
331 param_value is a dictionary, it's values will be unrolled
332 into individual parameters.
333 @type param_value: str
334 @raise ValueError: if parameters cannot be processed into valid Params
335 """
336
337
338 if not param_name:
339 raise ValueError("no parameter name specified")
340
341 if param_name == '/' and type(param_value) != dict:
342 raise ValueError("Cannot load non-dictionary types into global namespace '/'")
343
344 if type(param_value) == dict:
345
346 for k, v in param_value.iteritems():
347 self.add_param(ros_config, ns_join(param_name, k), v, verbose=verbose)
348 else:
349 ros_config.add_param(Param(param_name, param_value), verbose=verbose)
350
351 - def load_rosparam(self, context, ros_config, cmd, param, file, text, verbose=True):
352 """
353 Load rosparam setting
354
355 @param context: Loader context
356 @type context: L{LoaderContext}
357 @param ros_config: launch configuration
358 @type ros_config: L{ROSLaunchConfig}
359 @param cmd: 'load', 'dump', or 'delete'
360 @type cmd: str
361 @param file: filename for rosparam to use or None
362 @type file: str
363 @param text: text for rosparam to load. Ignored if file is set.
364 @type text: str
365 @raise ValueError: if parameters cannot be processed into valid rosparam setting
366 """
367 if not cmd in ('load', 'dump', 'delete'):
368 raise ValueError("command must be 'load', 'dump', or 'delete'")
369 if file is not None:
370 if cmd == 'load' and not os.path.isfile(file):
371 raise ValueError("file does not exist [%s]"%file)
372 if cmd == 'delete':
373 raise ValueError("'file' attribute is invalid with 'delete' command.")
374
375 full_param = ns_join(context.ns, param) if param else context.ns
376
377 if cmd == 'dump':
378 ros_config.add_executable(RosbinExecutable('rosparam', (cmd, file, full_param), PHASE_SETUP))
379 elif cmd == 'delete':
380 ros_config.add_executable(RosbinExecutable('rosparam', (cmd, full_param), PHASE_SETUP))
381 elif cmd == 'load':
382
383 if file:
384 with open(file, 'r') as f:
385 text = f.read()
386
387 if not text:
388 if file:
389 raise ValueError("no YAML in file %s"%file)
390 else:
391 raise ValueError("no YAML to load")
392
393
394
395 global yaml
396 if yaml is None:
397 import yaml
398 try:
399 data = yaml.load(text)
400 except yaml.MarkedYAMLError, e:
401 if not file:
402 raise ValueError("Error within YAML block:\n\t%s\n\nYAML is:\n%s"%(str(e), text))
403 else:
404 raise ValueError("file %s contains invalid YAML:\n%s"%(file, str(e)))
405 except Exception, e:
406 if not file:
407 raise ValueError("invalid YAML: %s\n\nYAML is:\n%s"%(str(e), text))
408 else:
409 raise ValueError("file %s contains invalid YAML:\n%s"%(file, str(e)))
410
411
412 if not param and type(data) != dict:
413 raise ValueError("'param' attribute must be set for non-dictionary values")
414
415 self.add_param(ros_config, full_param, data, verbose=verbose)
416
417 else:
418 raise ValueError("unknown command %s"%cmd)
419
420
421 - def load_env(self, context, ros_config, name, value):
422 """
423 Load environment variable setting
424
425 @param context: Loader context
426 @type context: L{LoaderContext}
427 @param ros_config: launch configuration
428 @type ros_config: L{ROSLaunchConfig}
429 @param name: environment variable name
430 @type name: str
431 @param value: environment variable value
432 @type value: str
433 """
434 if not name:
435 raise ValueError("'name' attribute must be non-empty")
436 context.env_args.append((name, value))
437
438
439 - def param_value(self, verbose, name, ptype, value, textfile, binfile, command):
440 """
441 Parse text representation of param spec into Python value
442 @param name: param name, for error message use only
443 @type name: str
444 @param verbose: print verbose output
445 @type verbose: bool
446 @param textfile: name of text file to load from, or None
447 @type textfile: str
448 @param binfile: name of binary file to load from, or None
449 @type binfile: str
450 @param command: command to execute for parameter value, or None
451 @type command: str
452 @raise ValueError: if parameters are invalid
453 """
454 if value is not None:
455 return convert_value(value.strip(), ptype)
456 elif textfile is not None:
457 with open(textfile, 'r') as f:
458 return f.read()
459 elif binfile is not None:
460 import xmlrpclib
461 with open(binfile, 'rb') as f:
462 return xmlrpclib.Binary(f.read())
463 elif command is not None:
464 if type(command) == unicode:
465 command = command.encode('UTF-8')
466 if verbose:
467 print "... executing command param [%s]"%command
468 import subprocess, shlex
469 try:
470 p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
471 c_value = p.communicate()[0]
472 if p.returncode != 0:
473 raise ValueError("Cannot load command parameter [%s]: command [%s] returned with code [%s]"%(name, command, p.returncode))
474 except OSError, (errno, strerr):
475 if errno == 2:
476 raise ValueError("Cannot load command parameter [%s]: no such command [%s]"%(name, command))
477 raise
478 if c_value is None:
479 raise ValueError("parameter: unable to get output of command [%s]"%command)
480 return c_value
481 else:
482 raise ValueError("unable to determine parameter value")
483