Package roslaunch :: Module substitution_args
[frames] | no frames]

Source Code for Module roslaunch.substitution_args

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2008, Willow Garage, Inc. 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32  # 
 33  # Revision $Id: substitution_args.py 15178 2011-10-10 21:22:53Z kwc $ 
 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 # Python 2.x 
 44  except ImportError: 
 45      from io import StringIO # Python 3.x 
 46   
 47  import rosgraph.names 
 48  import rospkg 
 49   
 50  _rospack = None 
 51   
52 -class SubstitutionException(Exception):
53 """ 54 Base class for exceptions in substitution_args routines 55 """ 56 pass
57 -class ArgException(SubstitutionException):
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 # #1559 #1660 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
151 -def _find_executable(resolved, a, args, _context):
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 # we try to find the specific executable in libexec via catkin 164 # which will search in install/devel space 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) # implicitly first_match_only=True 168 if paths: 169 full_path = _get_executable_path(paths[0], os.path.basename(path)) 170 if not full_path: 171 # else we will look for the executable in the source folder of the package 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
179 -def _find_resource(resolved, a, args, _context):
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 # we try to find the specific path in share via catkin 191 # which will search in install/devel space and the source folder of the package 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
199 -def _split_command(resolved, command_with_args):
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
206 -def _separate_first_path(value):
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
215 -def _sanitize_path(path):
216 path = path.replace('/', os.sep) 217 path = path.replace('\\', os.sep) 218 return path
219 220
221 -def _get_executable_path(base_path, path):
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
228 -def _get_rospack():
229 global _rospack 230 if _rospack is None: 231 _rospack = rospkg.RosPack() 232 return _rospack
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
259 -def resolve_args(arg_str, context=None, resolve_anon=True):
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 #parse found substitution args 285 if not arg_str: 286 return arg_str 287 # first resolve variables like 'env' and 'arg' 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 # than resolve 'find' as it requires the subsequent path to be expanded already 296 commands = { 297 'find': _find, 298 } 299 resolved = _resolve_args(resolved, context, resolve_anon, commands) 300 return resolved
301
302 -def _resolve_args(arg_str, context, resolve_anon, commands):
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
319 -def _collect_args(arg_str):
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 # No escapes supported 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 #save contents of collected buffer 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 # left paren must immediately follow dollar sign to enter _IN state 358 state = _OUT 359 elif state == _LP: 360 state = _IN 361 362 if state == _IN: 363 buff.write(c) 364 return args
365