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 Library for processing XML substitution args. This is currently used
37 by roslaunch and xacro, but it is not yet a top-level ROS feature.
38 """
39
40 import os
41
42 try:
43 from cStringIO import StringIO
44 except ImportError:
45 from io import StringIO
46
47 import rosgraph.names
48 import rospkg
49
50 _rospack = None
51
53 """
54 Base class for exceptions in substitution_args routines
55 """
56 pass
58 """
59 Exception for missing $(arg) values
60 """
61 pass
62
63 -def _env(resolved, a, args, context):
64 """
65 process $(env) arg
66 @return: updated resolved argument
67 @rtype: str
68 @raise SubstitutionException: if arg invalidly specified
69 """
70 if len(args) != 1:
71 raise SubstitutionException("$(env var) command only accepts one argument [%s]"%a)
72 try:
73 return resolved.replace("$(%s)"%a, os.environ[args[0]])
74 except KeyError as e:
75 raise SubstitutionException("environment variable %s is not set"%str(e))
76
77 -def _optenv(resolved, a, args, context):
78 """
79 process $(optenv) arg
80 @return: updated resolved argument
81 @rtype: str
82 @raise SubstitutionException: if arg invalidly specified
83 """
84 if len(args) == 0:
85 raise SubstitutionException("$(optenv var) must specify an environment variable [%s]"%a)
86 if args[0] in os.environ:
87 return resolved.replace("$(%s)"%a, os.environ[args[0]])
88 elif len(args) > 1:
89 return resolved.replace("$(%s)"%a, ' '.join(args[1:]))
90 else:
91 return resolved.replace("$(%s)"%a, '')
92
93 -def _anon(resolved, a, args, context):
94 """
95 process $(anon) arg
96 @return: updated resolved argument
97 @rtype: str
98 @raise SubstitutionException: if arg invalidly specified
99 """
100
101 if len(args) == 0:
102 raise SubstitutionException("$(anon var) must specify a name [%s]"%a)
103 elif len(args) > 1:
104 raise SubstitutionException("$(anon var) may only specify one name [%s]"%a)
105 id = args[0]
106 if 'anon' not in context:
107 context['anon'] = {}
108 anon_context = context['anon']
109 if id in anon_context:
110 return resolved.replace("$(%s)"%a, anon_context[id])
111 else:
112 resolve_to = rosgraph.names.anonymous_name(id)
113 anon_context[id] = resolve_to
114 return resolved.replace("$(%s)"%a, resolve_to)
115
116
117 -def _find(resolved, a, args, context):
118 """
119 process $(find PKG)
120 Resolves the path while considering the path following the command to provide backward compatible results.
121 If it is followed by a path it first tries to resolve it as an executable and than as a normal file under share.
122 Else it resolves to the source share folder of the PKG.
123 :returns: updated resolved argument, ``str``
124 :raises: :exc:SubstitutionException: if PKG invalidly specified
125 :raises: :exc:`rospkg.ResourceNotFound` If PKG requires resource (e.g. package) that does not exist
126 """
127 if len(args) != 1:
128 raise SubstitutionException("$(find pkg) command only accepts one argument [%s]" % a)
129 before, after = _split_command(resolved, a)
130 path, after = _separate_first_path(after)
131 resolve_without_path = before + ('$(%s)' % a) + after
132 path = _sanitize_path(path)
133 if path.startswith('/') or path.startswith('\\'):
134 path = path[1:]
135 if path:
136 try:
137 return _find_executable(resolve_without_path, a, [args[0], path], context)
138 except SubstitutionException:
139 pass
140 try:
141 return _find_resource(resolve_without_path, a, [args[0], path], context)
142 except SubstitutionException:
143 pass
144 rp = _get_rospack()
145 pkg_path = rp.get_path(args[0])
146 if path:
147 pkg_path = os.path.join(pkg_path, path)
148 return before + pkg_path + after
149
150
152 """
153 process $(find-executable PKG PATH)
154 It finds the executable with the basename(PATH) in the libexec folder
155 or under the PATH relative to the package.xml file.
156 :returns: updated resolved argument, ``str``
157 :raises: :exc:SubstitutionException: if PKG/PATH invalidly specified or executable is not found for PKG
158 """
159 if len(args) != 2:
160 raise SubstitutionException("$(find-executable pkg path) command only accepts two argument [%s]" % a)
161 before, after = _split_command(resolved, a)
162 path = _sanitize_path(args[1])
163
164
165 full_path = None
166 from catkin.find_in_workspaces import find_in_workspaces
167 paths = find_in_workspaces(['libexec'], project=args[0], first_matching_workspace_only=True)
168 if paths:
169 full_path = _get_executable_path(paths[0], os.path.basename(path))
170 if not full_path:
171
172 rp = _get_rospack()
173 full_path = _get_executable_path(rp.get_path(args[0]), path)
174 if not full_path:
175 raise SubstitutionException("$(find-executable pkg path) could not find executable [%s]" % a)
176 return before + full_path + after
177
178
180 """
181 process $(find-resource PKG PATH)
182 Resolves the relative PATH from the share folder of the PKG either from install space, devel space or from the source folder.
183 :returns: updated resolved argument, ``str``
184 :raises: :exc:SubstitutionException: if PKG and PATH invalidly specified or relative PATH is not found for PKG
185 """
186 if len(args) != 2:
187 raise SubstitutionException("$(find-resource pkg path) command only accepts two argument [%s]" % a)
188 before, after = _split_command(resolved, a)
189 path = _sanitize_path(args[1])
190
191
192 from catkin.find_in_workspaces import find_in_workspaces
193 paths = find_in_workspaces(['share'], project=args[0], path=path, first_matching_workspace_only=True, first_match_only=True)
194 if not paths:
195 raise SubstitutionException("$(find-resource pkg path) could not find path [%s]" % a)
196 return before + paths[0] + after
197
198
200 cmd = '$(%s)' % command_with_args
201 idx1 = resolved.find(cmd)
202 idx2 = idx1 + len(cmd)
203 return resolved[0:idx1], resolved[idx2:]
204
205
207 idx = value.find(' ')
208 if idx < 0:
209 path, rest = value, ''
210 else:
211 path, rest = value[0:idx], value[idx:]
212 return path, rest
213
214
216 path = path.replace('/', os.sep)
217 path = path.replace('\\', os.sep)
218 return path
219
220
222 full_path = os.path.join(base_path, path)
223 if os.path.isfile(full_path) and os.access(full_path, os.X_OK):
224 return full_path
225 return None
226
227
233
234
235 -def _arg(resolved, a, args, context):
236 """
237 process $(arg) arg
238
239 :returns: updated resolved argument, ``str``
240 :raises: :exc:`ArgException` If arg invalidly specified
241 """
242 if len(args) == 0:
243 raise SubstitutionException("$(arg var) must specify an environment variable [%s]"%(a))
244 elif len(args) > 1:
245 raise SubstitutionException("$(arg var) may only specify one arg [%s]"%(a))
246
247 if 'arg' not in context:
248 context['arg'] = {}
249 arg_context = context['arg']
250
251 arg_name = args[0]
252 if arg_name in arg_context:
253 arg_value = arg_context[arg_name]
254 return resolved.replace("$(%s)"%a, arg_value)
255 else:
256 raise ArgException(arg_name)
257
258
260 """
261 Resolves substitution args (see wiki spec U{http://ros.org/wiki/roslaunch}).
262
263 @param arg_str: string to resolve zero or more substitution args
264 in. arg_str may be None, in which case resolve_args will
265 return None
266 @type arg_str: str
267 @param context dict: (optional) dictionary for storing results of
268 the 'anon' and 'arg' substitution args. multiple calls to
269 resolve_args should use the same context so that 'anon'
270 substitions resolve consistently. If no context is provided, a
271 new one will be created for each call. Values for the 'arg'
272 context should be stored as a dictionary in the 'arg' key.
273 @type context: dict
274 @param resolve_anon bool: If True (default), will resolve $(anon
275 foo). If false, will leave these args as-is.
276 @type resolve_anon: bool
277
278 @return str: arg_str with substitution args resolved
279 @rtype: str
280 @raise SubstitutionException: if there is an error resolving substitution args
281 """
282 if context is None:
283 context = {}
284
285 if not arg_str:
286 return arg_str
287
288 commands = {
289 'env': _env,
290 'optenv': _optenv,
291 'anon': _anon,
292 'arg': _arg,
293 }
294 resolved = _resolve_args(arg_str, context, resolve_anon, commands)
295
296 commands = {
297 'find': _find,
298 }
299 resolved = _resolve_args(resolved, context, resolve_anon, commands)
300 return resolved
301
303 valid = ['find', 'env', 'optenv', 'anon', 'arg']
304 resolved = arg_str
305 for a in _collect_args(arg_str):
306 splits = [s for s in a.split(' ') if s]
307 if not splits[0] in valid:
308 raise SubstitutionException("Unknown substitution command [%s]. Valid commands are %s"%(a, valid))
309 command = splits[0]
310 args = splits[1:]
311 if command in commands:
312 resolved = commands[command](resolved, a, args, context)
313 return resolved
314
315 _OUT = 0
316 _DOLLAR = 1
317 _LP = 2
318 _IN = 3
320 """
321 State-machine parser for resolve_args. Substitution args are of the form:
322 $(find package_name)/scripts/foo.py $(export some/attribute blar) non-relevant stuff
323
324 @param arg_str: argument string to parse args from
325 @type arg_str: str
326 @raise SubstitutionException: if args are invalidly specified
327 @return: list of arguments
328 @rtype: [str]
329 """
330 buff = StringIO()
331 args = []
332 state = _OUT
333 for c in arg_str:
334
335 if c == '$':
336 if state == _OUT:
337 state = _DOLLAR
338 elif state == _DOLLAR:
339 pass
340 else:
341 raise SubstitutionException("Dollar signs '$' cannot be inside of substitution args [%s]"%arg_str)
342 elif c == '(':
343 if state == _DOLLAR:
344 state = _LP
345 elif state != _OUT:
346 raise SubstitutionException("Invalid left parenthesis '(' in substitution args [%s]"%arg_str)
347 elif c == ')':
348 if state == _IN:
349
350 args.append(buff.getvalue())
351 buff.truncate(0)
352 buff.seek(0)
353 state = _OUT
354 else:
355 state = _OUT
356 elif state == _DOLLAR:
357
358 state = _OUT
359 elif state == _LP:
360 state = _IN
361
362 if state == _IN:
363 buff.write(c)
364 return args
365