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