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 import errno
40 import os
41 from copy import deepcopy
42
43 import yaml
44
45 from roslaunch.core import Param, RosbinExecutable, RLException, PHASE_SETUP
46
47 from rosgraph.names import make_global_ns, ns_join, PRIV_NAME, load_mappings, is_legal_name, canonicalize_name
48
49
50 rosparam = None
51
53 """Error loading data as specified (e.g. cannot find included files, etc...)"""
54 pass
55
56
58 """
59 Convert a value from a string representation into the specified
60 type
61
62 @param value: string representation of value
63 @type value: str
64 @param type_: int, double, string, bool, or auto
65 @type type_: str
66 @raise ValueError: if parameters are invalid
67 """
68 type_ = type_.lower()
69
70
71 if type_ == 'auto':
72
73 try:
74 if '.' in value:
75 return float(value)
76 else:
77 return int(value)
78 except ValueError as e:
79 pass
80
81 lval = value.lower()
82 if lval == 'true' or lval == 'false':
83 return convert_value(value, 'bool')
84
85 return value
86 elif type_ == 'str' or type_ == 'string':
87 return value
88 elif type_ == 'int':
89 return int(value)
90 elif type_ == 'double':
91 return float(value)
92 elif type_ == 'bool' or type_ == 'boolean':
93 value = value.lower().strip()
94 if value == 'true' or value == '1':
95 return True
96 elif value == 'false' or value == '0':
97 return False
98 raise ValueError("%s is not a '%s' type"%(value, type_))
99 elif type_ == 'yaml':
100 try:
101 return yaml.load(value)
102 except yaml.parser.ParserError as e:
103 raise ValueError(e)
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 = list(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 self.pass_all_args = False
184
185 - def add_param(self, p):
186 """
187 Add a ~param to the context. ~params are evaluated by any node
188 declarations that occur later in the same context.
189
190 @param p: parameter
191 @type p: L{Param}
192 """
193
194
195 matches = [m for m in self.params if m.key == p.key]
196 for m in matches:
197 self.params.remove(m)
198 self.params.append(p)
199
200 - def add_remap(self, remap):
201 """
202 Add a new remap setting to the context. if a remap already
203 exists with the same from key, it will be removed
204
205 @param remap: remap setting
206 @type remap: (str, str)
207 """
208 remap = [canonicalize_name(x) for x in remap]
209 if not remap[0] or not remap[1]:
210 raise RLException("remap from/to attributes cannot be empty")
211 if not is_legal_name(remap[0]):
212 raise RLException("remap from [%s] is not a valid ROS name"%remap[0])
213 if not is_legal_name(remap[1]):
214 raise RLException("remap to [%s] is not a valid ROS name"%remap[1])
215
216 matches = [r for r in self._remap_args if r[0] == remap[0]]
217 for m in matches:
218 self._remap_args.remove(m)
219 self._remap_args.append(remap)
220
221 - def add_arg(self, name, default=None, value=None, doc=None):
222 """
223 Add 'arg' to existing context. Args are only valid for their immediate context.
224 """
225 if name in self.arg_names:
226
227 if not self.pass_all_args:
228 raise LoadException("arg '%s' has already been declared"%name)
229 else:
230 self.arg_names.append(name)
231
232 resolve_dict = self.resolve_dict if self.include_resolve_dict is None else self.include_resolve_dict
233
234 if not 'arg' in resolve_dict:
235 resolve_dict['arg'] = {}
236 arg_dict = resolve_dict['arg']
237
238
239
240 if value is not None:
241
242
243
244
245 if name in arg_dict and not self.pass_all_args:
246 raise LoadException("cannot override arg '%s', which has already been set"%name)
247 arg_dict[name] = value
248 elif default is not None:
249
250 if name not in arg_dict:
251 arg_dict[name] = default
252 else:
253
254
255 pass
256
257
258 if not 'arg_doc' in resolve_dict:
259 resolve_dict['arg_doc'] = {}
260 arg_doc_dict = resolve_dict['arg_doc']
261
262 if not value:
263
264 arg_doc_dict[name] = (doc, default)
265
266
267 - def remap_args(self):
268 """
269 @return: copy of the current remap arguments
270 @rtype: [(str, str)]
271 """
272 if self.parent:
273 args = []
274
275 for pr in self.parent.remap_args():
276 if not [r for r in self._remap_args if r[0] == pr[0]]:
277 args.append(pr)
278 args.extend(self._remap_args)
279 return args
280 return self._remap_args[:]
281
282 - def include_child(self, ns, filename):
283 """
284 Create child namespace based on include inheritance rules
285 @param ns: sub-namespace of child context, or None if the
286 child context shares the same namespace
287 @type ns: str
288 @param filename: name of include file
289 @type filename: str
290 @return: A child xml context that inherits from this context
291 @rtype: L{LoaderContext}jj
292 """
293 ctx = self.child(ns)
294
295 ctx.arg_names = []
296 ctx.filename = filename
297
298 ctx.include_resolve_dict = {}
299
300 return ctx
301
302 - def child(self, ns):
303 """
304 @param ns: sub-namespace of child context, or None if the
305 child context shares the same namespace
306 @type ns: str
307 @return: A child xml context that inherits from this context
308 @rtype: L{LoaderContext}
309 """
310 if ns:
311 if ns[0] == '/':
312 child_ns = ns
313 elif ns == PRIV_NAME:
314
315 child_ns = PRIV_NAME
316 else:
317 child_ns = ns_join(self.ns, ns)
318 else:
319 child_ns = self.ns
320 return LoaderContext(child_ns, self.filename, parent=self,
321 params=self.params, env_args=self.env_args[:],
322 resolve_dict=deepcopy(self.resolve_dict),
323 arg_names=self.arg_names[:], include_resolve_dict=self.include_resolve_dict)
324
325
326
327
328
329
331 """
332 Lower-level library for loading ROS launch model. It provides an
333 abstraction between the representation (e.g. XML) and the
334 validation of the property values.
335 """
336
337 - def add_param(self, ros_config, param_name, param_value, verbose=True):
338 """
339 Add L{Param} instances to launch config. Dictionary values are
340 unrolled into individual parameters.
341
342 @param ros_config: launch configuration
343 @type ros_config: L{ROSLaunchConfig}
344 @param param_name: name of parameter namespace to load values
345 into. If param_name is '/', param_value must be a dictionary
346 @type param_name: str
347 @param param_value: value to assign to param_name. If
348 param_value is a dictionary, it's values will be unrolled
349 into individual parameters.
350 @type param_value: str
351 @raise ValueError: if parameters cannot be processed into valid Params
352 """
353
354
355 if not param_name:
356 raise ValueError("no parameter name specified")
357
358 if param_name == '/' and type(param_value) != dict:
359 raise ValueError("Cannot load non-dictionary types into global namespace '/'")
360
361 if type(param_value) == dict:
362
363 for k, v in param_value.items():
364 self.add_param(ros_config, ns_join(param_name, k), v, verbose=verbose)
365 else:
366 ros_config.add_param(Param(param_name, param_value), verbose=verbose)
367
368 - def load_rosparam(self, context, ros_config, cmd, param, file_, text, verbose=True, subst_function=None):
369 """
370 Load rosparam setting
371
372 @param context: Loader context
373 @type context: L{LoaderContext}
374 @param ros_config: launch configuration
375 @type ros_config: L{ROSLaunchConfig}
376 @param cmd: 'load', 'dump', or 'delete'
377 @type cmd: str
378 @param file_: filename for rosparam to use or None
379 @type file_: str
380 @param text: text for rosparam to load. Ignored if file_ is set.
381 @type text: str
382 @raise ValueError: if parameters cannot be processed into valid rosparam setting
383 """
384 if not cmd in ('load', 'dump', 'delete'):
385 raise ValueError("command must be 'load', 'dump', or 'delete'")
386 if file_ is not None:
387 if cmd == 'load' and not os.path.isfile(file_):
388 raise ValueError("file does not exist [%s]"%file_)
389 if cmd == 'delete':
390 raise ValueError("'file' attribute is invalid with 'delete' command.")
391
392 full_param = ns_join(context.ns, param) if param else context.ns
393
394 if cmd == 'dump':
395 ros_config.add_executable(RosbinExecutable('rosparam', (cmd, file_, full_param), PHASE_SETUP))
396 elif cmd == 'delete':
397 ros_config.add_executable(RosbinExecutable('rosparam', (cmd, full_param), PHASE_SETUP))
398 elif cmd == 'load':
399
400 if file_:
401 with open(file_, 'r') as f:
402 text = f.read()
403
404 if subst_function is not None:
405 text = subst_function(text)
406
407
408 global rosparam
409 if rosparam is None:
410 import rosparam
411 try:
412 data = yaml.load(text)
413
414
415
416 if data is None:
417 data = {}
418 except yaml.MarkedYAMLError as e:
419 if not file_:
420 raise ValueError("Error within YAML block:\n\t%s\n\nYAML is:\n%s"%(str(e), text))
421 else:
422 raise ValueError("file %s contains invalid YAML:\n%s"%(file_, str(e)))
423 except Exception as e:
424 if not file_:
425 raise ValueError("invalid YAML: %s\n\nYAML is:\n%s"%(str(e), text))
426 else:
427 raise ValueError("file %s contains invalid YAML:\n%s"%(file_, str(e)))
428
429
430 if not param and type(data) != dict:
431 raise ValueError("'param' attribute must be set for non-dictionary values")
432
433 self.add_param(ros_config, full_param, data, verbose=verbose)
434
435 else:
436 raise ValueError("unknown command %s"%cmd)
437
438
439 - def load_env(self, context, ros_config, name, value):
440 """
441 Load environment variable setting
442
443 @param context: Loader context
444 @type context: L{LoaderContext}
445 @param ros_config: launch configuration
446 @type ros_config: L{ROSLaunchConfig}
447 @param name: environment variable name
448 @type name: str
449 @param value: environment variable value
450 @type value: str
451 """
452 if not name:
453 raise ValueError("'name' attribute must be non-empty")
454 context.env_args.append((name, value))
455
456
457 - def param_value(self, verbose, name, ptype, value, textfile, binfile, command):
458 """
459 Parse text representation of param spec into Python value
460 @param name: param name, for error message use only
461 @type name: str
462 @param verbose: print verbose output
463 @type verbose: bool
464 @param textfile: name of text file to load from, or None
465 @type textfile: str
466 @param binfile: name of binary file to load from, or None
467 @type binfile: str
468 @param command: command to execute for parameter value, or None
469 @type command: str
470 @raise ValueError: if parameters are invalid
471 """
472 if value is not None:
473 return convert_value(value.strip(), ptype)
474 elif textfile is not None:
475 with open(textfile, 'r') as f:
476 return convert_value(f.read(), ptype)
477 elif binfile is not None:
478 try:
479 from xmlrpc.client import Binary
480 except ImportError:
481 from xmlrpclib import Binary
482 with open(binfile, 'rb') as f:
483 return Binary(f.read())
484 elif command is not None:
485 try:
486 if type(command) == unicode:
487 command = command.encode('utf-8')
488 except NameError:
489 pass
490 if verbose:
491 print("... executing command param [%s]" % command)
492 import subprocess, shlex
493 try:
494 p = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE)
495 c_value = p.communicate()[0]
496 if not isinstance(c_value, str):
497 c_value = c_value.decode('utf-8')
498 if p.returncode != 0:
499 raise ValueError("Cannot load command parameter [%s]: command [%s] returned with code [%s]"%(name, command, p.returncode))
500 except OSError as e:
501 if e.errno == errno.ENOENT:
502 raise ValueError("Cannot load command parameter [%s]: no such command [%s]"%(name, command))
503 raise
504 if c_value is None:
505 raise ValueError("parameter: unable to get output of command [%s]"%command)
506 return convert_value(c_value, ptype)
507 else:
508 raise ValueError("unable to determine parameter value")
509