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 roslib.exceptions
48 import roslib.names
49 import roslib.packages
50
52 """
53 Base class for exceptions in roslib.substitution_args routines
54 """
55 pass
57 """
58 Exception for missing $(arg) values
59 """
60 pass
61
62 -def _env(resolved, a, args, context):
63 """
64 process $(env) arg
65 @return: updated resolved argument
66 @rtype: str
67 @raise SubstitutionException: if arg invalidly specified
68 """
69 if len(args) != 1:
70 raise SubstitutionException("$(env var) command only accepts one argument [%s]"%a)
71 try:
72 return resolved.replace("$(%s)"%a, os.environ[args[0]])
73 except KeyError as e:
74 raise SubstitutionException("environment variable %s is not set"%str(e))
75
76 -def _optenv(resolved, a, args, context):
77 """
78 process $(optenv) arg
79 @return: updated resolved argument
80 @rtype: str
81 @raise SubstitutionException: if arg invalidly specified
82 """
83 if len(args) == 0:
84 raise SubstitutionException("$(optenv var) must specify an environment variable [%s]"%a)
85 if args[0] in os.environ:
86 return resolved.replace("$(%s)"%a, os.environ[args[0]])
87 elif len(args) > 1:
88 return resolved.replace("$(%s)"%a, ' '.join(args[1:]))
89 else:
90 return resolved.replace("$(%s)"%a, '')
91
92 -def _anon(resolved, a, args, context):
93 """
94 process $(anon) arg
95 @return: updated resolved argument
96 @rtype: str
97 @raise SubstitutionException: if arg invalidly specified
98 """
99
100 if len(args) == 0:
101 raise SubstitutionException("$(anon var) must specify a name [%s]"%a)
102 elif len(args) > 1:
103 raise SubstitutionException("$(anon var) may only specify one name [%s]"%a)
104 id = args[0]
105 if 'anon' not in context:
106 context['anon'] = {}
107 anon_context = context['anon']
108 if id in anon_context:
109 return resolved.replace("$(%s)"%a, anon_context[id])
110 else:
111 resolve_to = roslib.names.anonymous_name(id)
112 anon_context[id] = resolve_to
113 return resolved.replace("$(%s)"%a, resolve_to)
114
115 -def _find(resolved, a, args, context):
116 """
117 process $(find) arg
118 @return: updated resolved argument
119 @rtype: str
120 @raise SubstitutionException: if arg invalidly specified
121 """
122 if len(args) != 1:
123 raise SubstitutionException("$(find pkg) command only accepts one argument [%s]"%a)
124 arg = "$(%s)"%a
125 sep = os.sep
126
127
128
129
130
131 idx = resolved.find(arg)+len(arg)
132 endidx = resolved.find(' ', idx)
133 if endidx < 0:
134 endidx = len(resolved)
135 slash_orig = resolved[idx:endidx]
136 resolved = resolved.replace(slash_orig, slash_orig.replace('/', sep))
137 resolved = resolved.replace(slash_orig, slash_orig.replace('\\', sep))
138
139 return resolved[0:idx-len(arg)] + roslib.packages.get_pkg_dir(args[0]) + resolved[idx:]
140
141 -def _arg(resolved, a, args, context):
142 """
143 process $(arg) arg
144
145 @return: updated resolved argument
146 @rtype: str
147 @raise ArgException: if arg invalidly specified
148 """
149 if len(args) == 0:
150 raise SubstitutionException("$(arg var) must specify an environment variable [%s]"%(a))
151 elif len(args) > 1:
152 raise SubstitutionException("$(arg var) may only specify one arg [%s]"%(a))
153
154 if 'arg' not in context:
155 context['arg'] = {}
156 arg_context = context['arg']
157
158 arg_name = args[0]
159 if arg_name in arg_context:
160 arg_value = arg_context[arg_name]
161 return resolved.replace("$(%s)"%a, arg_value)
162 else:
163 raise ArgException(arg_name)
164
165
167 """
168 Resolves substitution args (see wiki spec U{http://ros.org/wiki/roslaunch}).
169
170 @param arg_str: string to resolve zero or more substitution args
171 in. arg_str may be None, in which case resolve_args will
172 return None
173 @type arg_str: str
174 @param context dict: (optional) dictionary for storing results of
175 the 'anon' and 'arg' substitution args. multiple calls to
176 resolve_args should use the same context so that 'anon'
177 substitions resolve consistently. If no context is provided, a
178 new one will be created for each call. Values for the 'arg'
179 context should be stored as a dictionary in the 'arg' key.
180 @type context: dict
181 @param resolve_anon bool: If True (default), will resolve $(anon
182 foo). If false, will leave these args as-is.
183 @type resolve_anon: bool
184
185 @return str: arg_str with substitution args resolved
186 @rtype: str
187 @raise SubstitutionException: if there is an error resolving substitution args
188 """
189 if context is None:
190 context = {}
191
192 if not arg_str:
193 return arg_str
194 valid = ['find', 'env', 'optenv', 'anon', 'arg']
195
196 resolved = arg_str
197 for a in _collect_args(arg_str):
198 splits = [s for s in a.split(' ') if s]
199 if not splits[0] in valid:
200 raise SubstitutionException("Unknown substitution command [%s]. Valid commands are %s"%(a, valid))
201 command = splits[0]
202 args = splits[1:]
203 if command == 'find':
204 resolved = _find(resolved, a, args, context)
205 elif command == 'env':
206 resolved = _env(resolved, a, args, context)
207 elif command == 'optenv':
208 resolved = _optenv(resolved, a, args, context)
209 elif command == 'anon' and resolve_anon:
210 resolved = _anon(resolved, a, args, context)
211 elif command == 'arg':
212 resolved = _arg(resolved, a, args, context)
213
214 return resolved
215
216 _OUT = 0
217 _DOLLAR = 1
218 _LP = 2
219 _IN = 3
221 """
222 State-machine parser for resolve_args. Substitution args are of the form:
223 $(find rospy)/scripts/foo.py $(export some/attribute blar) non-relevant stuff
224
225 @param arg_str: argument string to parse args from
226 @type arg_str: str
227 @raise SubstitutionException: if args are invalidly specified
228 @return: list of arguments
229 @rtype: [str]
230 """
231 buff = StringIO()
232 args = []
233 state = _OUT
234 for c in arg_str:
235
236 if c == '$':
237 if state == _OUT:
238 state = _DOLLAR
239 elif state == _DOLLAR:
240 pass
241 else:
242 raise SubstitutionException("Dollar signs '$' cannot be inside of substitution args [%s]"%arg_str)
243 elif c == '(':
244 if state == _DOLLAR:
245 state = _LP
246 elif state != _OUT:
247 raise SubstitutionException("Invalid left parenthesis '(' in substitution args [%s]"%arg_str)
248 elif c == ')':
249 if state == _IN:
250
251 args.append(buff.getvalue())
252 buff.truncate(0)
253 buff.seek(0)
254 state = _OUT
255 else:
256 state = _OUT
257 elif state == _DOLLAR:
258
259 state = _OUT
260 elif state == _LP:
261 state = _IN
262
263 if state == _IN:
264 buff.write(c)
265 return args
266