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