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