You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
6.9 KiB
218 lines
6.9 KiB
#!/usr/bin/env python3
|
|
"""
|
|
Library for processing XML substitution args.
|
|
"""
|
|
|
|
# Software License Agreement (BSD License)
|
|
#
|
|
# Copyright (c) 2008, Willow Garage, Inc.
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions
|
|
# are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above
|
|
# copyright notice, this list of conditions and the following
|
|
# disclaimer in the documentation and/or other materials provided
|
|
# with the distribution.
|
|
# * Neither the name of Willow Garage, Inc. nor the names of its
|
|
# contributors may be used to endorse or promote products derived
|
|
# from this software without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
#
|
|
# Revision $Id: substitution_args.py 15178 2011-10-10 21:22:53Z kwc $
|
|
|
|
from io import StringIO
|
|
import os
|
|
|
|
|
|
class SubstitutionException(Exception):
|
|
"""
|
|
Base class for exceptions in substitution_args routines
|
|
"""
|
|
pass
|
|
|
|
|
|
class ArgException(SubstitutionException):
|
|
"""
|
|
Exception for missing $(arg) values
|
|
"""
|
|
pass
|
|
|
|
|
|
def _split_command(resolved, command_with_args):
|
|
cmd = '$(%s)' % command_with_args
|
|
idx1 = resolved.find(cmd)
|
|
idx2 = idx1 + len(cmd)
|
|
return resolved[0:idx1], resolved[idx2:]
|
|
|
|
|
|
def _separate_first_path(value):
|
|
idx = value.find(' ')
|
|
if idx < 0:
|
|
path, rest = value, ''
|
|
else:
|
|
path, rest = value[0:idx], value[idx:]
|
|
return path, rest
|
|
|
|
|
|
def _sanitize_path(path):
|
|
path = path.replace('/', os.sep)
|
|
path = path.replace('\\', os.sep)
|
|
return path
|
|
|
|
|
|
def _arg(resolved, a, args, context):
|
|
"""
|
|
process $(arg) arg
|
|
|
|
:returns: updated resolved argument, ``str``
|
|
:raises: :exc:`ArgException` If arg invalidly specified
|
|
"""
|
|
if len(args) == 0:
|
|
raise SubstitutionException(
|
|
"$(arg var) must specify an environment variable [%s]" %
|
|
(a))
|
|
elif len(args) > 1:
|
|
raise SubstitutionException(
|
|
"$(arg var) may only specify one arg [%s]" %
|
|
(a))
|
|
|
|
if 'arg' not in context:
|
|
context['arg'] = {}
|
|
arg_context = context['arg']
|
|
|
|
arg_name = args[0]
|
|
if arg_name in arg_context:
|
|
arg_value = arg_context[arg_name]
|
|
return resolved.replace("$(%s)" % a, arg_value)
|
|
else:
|
|
raise ArgException(arg_name)
|
|
|
|
|
|
def resolve_args(arg_str, context=None, resolve_anon=True):
|
|
"""
|
|
Resolves substitution args (see wiki spec U{http://ros.org/wiki/roslaunch}).
|
|
|
|
@param arg_str: string to resolve zero or more substitution args
|
|
in. arg_str may be None, in which case resolve_args will
|
|
return None
|
|
@type arg_str: str
|
|
@param context dict: (optional) dictionary for storing results of
|
|
the 'anon' and 'arg' substitution args. multiple calls to
|
|
resolve_args should use the same context so that 'anon'
|
|
substitions resolve consistently. If no context is provided, a
|
|
new one will be created for each call. Values for the 'arg'
|
|
context should be stored as a dictionary in the 'arg' key.
|
|
@type context: dict
|
|
@param resolve_anon bool: If True (default), will resolve $(anon
|
|
foo). If false, will leave these args as-is.
|
|
@type resolve_anon: bool
|
|
|
|
@return str: arg_str with substitution args resolved
|
|
@rtype: str
|
|
@raise SubstitutionException: if there is an error resolving substitution args
|
|
"""
|
|
if context is None:
|
|
context = {}
|
|
# parse found substitution args
|
|
if not arg_str:
|
|
return arg_str
|
|
# first resolve variables like 'env' and 'arg'
|
|
commands = {
|
|
'arg': _arg,
|
|
}
|
|
resolved = _resolve_args(arg_str, context, resolve_anon, commands)
|
|
# than resolve 'find' as it requires the subsequent path to be expanded
|
|
# already
|
|
|
|
resolved = _resolve_args(resolved, context, resolve_anon, commands)
|
|
return resolved
|
|
|
|
|
|
def _resolve_args(arg_str, context, resolve_anon, commands):
|
|
valid = ['arg']
|
|
resolved = arg_str
|
|
for a in _collect_args(arg_str):
|
|
splits = [s for s in a.split(' ') if s]
|
|
if not splits[0] in valid:
|
|
raise SubstitutionException(
|
|
"Unknown substitution command [%s]. Valid commands are %s" %
|
|
(a, valid))
|
|
command = splits[0]
|
|
args = splits[1:]
|
|
if command in commands:
|
|
resolved = commands[command](resolved, a, args, context)
|
|
return resolved
|
|
|
|
|
|
_OUT = 0
|
|
_DOLLAR = 1
|
|
_LP = 2
|
|
_IN = 3
|
|
|
|
|
|
def _collect_args(arg_str):
|
|
"""
|
|
State-machine parser for resolve_args. Substitution args are of the form:
|
|
$(find package_name)/scripts/foo.py $(export some/attribute blar) non-relevant stuff
|
|
|
|
@param arg_str: argument string to parse args from
|
|
@type arg_str: str
|
|
@raise SubstitutionException: if args are invalidly specified
|
|
@return: list of arguments
|
|
@rtype: [str]
|
|
"""
|
|
buff = StringIO()
|
|
args = []
|
|
state = _OUT
|
|
for c in arg_str:
|
|
# No escapes supported
|
|
if c == '$':
|
|
if state == _OUT:
|
|
state = _DOLLAR
|
|
elif state == _DOLLAR:
|
|
pass
|
|
else:
|
|
raise SubstitutionException(
|
|
"Dollar signs '$' cannot be inside of substitution args [%s]" %
|
|
arg_str)
|
|
elif c == '(':
|
|
if state == _DOLLAR:
|
|
state = _LP
|
|
elif state != _OUT:
|
|
raise SubstitutionException(
|
|
"Invalid left parenthesis '(' in substitution args [%s]" % arg_str)
|
|
elif c == ')':
|
|
if state == _IN:
|
|
# save contents of collected buffer
|
|
args.append(buff.getvalue())
|
|
buff.truncate(0)
|
|
buff.seek(0)
|
|
state = _OUT
|
|
else:
|
|
state = _OUT
|
|
elif state == _DOLLAR:
|
|
# left paren must immediately follow dollar sign to enter _IN state
|
|
state = _OUT
|
|
elif state == _LP:
|
|
state = _IN
|
|
|
|
if state == _IN:
|
|
buff.write(c)
|
|
return args
|
|
|