Skip to content

Commit

Permalink
Add $(dirname) to get directory of current launch file. (ros#1103)
Browse files Browse the repository at this point in the history
* Add $(dir) to get directory of current launch file.

* Change $(dir) -> $(dirname)

* Add simple tests for $(dirname) substitution.

* Add xmlloader test for $(dirname).
  • Loading branch information
mikepurvis authored and sputnick1124 committed Jul 30, 2017
1 parent ab272d5 commit e7840dd
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 4 deletions.
28 changes: 25 additions & 3 deletions tools/roslaunch/src/roslaunch/substitution_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ def _anon(resolved, a, args, context):
anon_context = context['anon']
return resolved.replace("$(%s)" % a, _eval_anon(id=args[0], anons=anon_context))

def _eval_dirname(filename):
if not filename:
raise SubstitutionException("Cannot substitute $(dirname), no file/directory information available.")
return os.path.abspath(os.path.dirname(filename))

def _dirname(resolved, a, args, context):
"""
process $(dirname)
@return: updated resolved argument
@rtype: str
@raise SubstitutionException: if no information about the current launch file is available, for example
if XML was passed via stdin, or this is a remote launch.
"""
return resolved.replace("$(%s)" % a, _eval_dirname(context.get('filename', None)))

def _eval_find(pkg):
rp = _get_rospack()
return rp.get_path(pkg)
Expand Down Expand Up @@ -316,7 +331,13 @@ def _eval(s, context):
def _eval_anon_context(id): return _eval_anon(id, anons=context['anon'])
# inject arg context
def _eval_arg_context(name): return convert_value(_eval_arg(name, args=context['arg']), 'auto')
functions = dict(anon=_eval_anon_context, arg=_eval_arg_context)
# inject dirname context
def _eval_dirname_context(): return _eval_dirname(context['filename'])
functions = {
'anon': _eval_anon_context,
'arg': _eval_arg_context,
'dirname': _eval_dirname_context
}
functions.update(_eval_dict)

# ignore values containing double underscores (for safety)
Expand All @@ -325,7 +346,7 @@ def _eval_arg_context(name): return convert_value(_eval_arg(name, args=context['
raise SubstitutionException("$(eval ...) may not contain double underscore expressions")
return str(eval(s, {}, _DictWrapper(context['arg'], functions)))

def resolve_args(arg_str, context=None, resolve_anon=True):
def resolve_args(arg_str, context=None, resolve_anon=True, filename=None):
"""
Resolves substitution args (see wiki spec U{http://ros.org/wiki/roslaunch}).
Expand Down Expand Up @@ -359,6 +380,7 @@ def resolve_args(arg_str, context=None, resolve_anon=True):
commands = {
'env': _env,
'optenv': _optenv,
'dirname': _dirname,
'anon': _anon,
'arg': _arg,
}
Expand All @@ -371,7 +393,7 @@ def resolve_args(arg_str, context=None, resolve_anon=True):
return resolved

def _resolve_args(arg_str, context, resolve_anon, commands):
valid = ['find', 'env', 'optenv', 'anon', 'arg']
valid = ['find', 'env', 'optenv', 'dirname', 'anon', 'arg']
resolved = arg_str
for a in _collect_args(arg_str):
splits = [s for s in a.split(' ') if s]
Expand Down
2 changes: 2 additions & 0 deletions tools/roslaunch/src/roslaunch/xmlloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ def resolve_args(self, args, context):
"""
# resolve_args gets called a lot, so we optimize by testing for dollar sign before resolving
if args and '$' in args:
# Populate resolve_dict with name of the current file being processed.
context.resolve_dict['filename'] = context.filename
return substitution_args.resolve_args(args, context=context.resolve_dict, resolve_anon=self.resolve_anon)
else:
return args
Expand Down
5 changes: 4 additions & 1 deletion tools/roslaunch/test/unit/test_substitution_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def test_resolve_args():

anon_context = {'foo': 'bar'}
arg_context = {'fuga': 'hoge', 'car': 'cdr', 'arg': 'foo', 'True': 'False'}
context = {'anon': anon_context, 'arg': arg_context }
context = {'anon': anon_context, 'arg': arg_context, 'filename': '/path/to/file.launch'}

tests = [
('$(find roslaunch)', roslaunch_dir),
Expand All @@ -119,6 +119,7 @@ def test_resolve_args():
('$(optenv NOT_ROS_ROOT)more stuff', 'more stuff'),
('$(optenv NOT_ROS_ROOT alternate)', 'alternate'),
('$(optenv NOT_ROS_ROOT alternate text)', 'alternate text'),
('$(dirname)/foo', '/path/to/foo'),

# #1776
('$(anon foo)', 'bar'),
Expand Down Expand Up @@ -177,6 +178,8 @@ def test_resolve_args():
'$(optenv)',
'$(anon)',
'$(anon foo bar)',
# Should fail without the supplied context.
'$(dirname)'
]
for f in failures:
try:
Expand Down
15 changes: 15 additions & 0 deletions tools/roslaunch/test/unit/test_xmlloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -1064,3 +1064,18 @@ def test_arg_all_includes(self):
# self.fail('should have thrown an exception')
#except roslaunch.xmlloader.XmlParseException:
# pass

# Test for $(dirname) behaviour across included files.
def test_dirname(self):
loader = roslaunch.xmlloader.XmlLoader()
filename = os.path.join(self.xml_dir, 'test-dirname.xml')

mock = RosLaunchMock()
loader.load(filename, mock)

param_d = {}
for p in mock.params:
param_d[p.key] = p.value

self.assertEquals(param_d['/foo'], self.xml_dir + '/bar')
self.assertEquals(param_d['/bar'], self.xml_dir + '/test-dirname/baz')
4 changes: 4 additions & 0 deletions tools/roslaunch/test/xml/test-dirname.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<launch>
<param name="foo" value="$(dirname)/bar" />
<include file="$(dirname)/test-dirname/included.xml" />
</launch>
3 changes: 3 additions & 0 deletions tools/roslaunch/test/xml/test-dirname/included.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<launch>
<param name="bar" value="$(dirname)/baz" />
</launch>

0 comments on commit e7840dd

Please sign in to comment.