Repository: ros/ros_comm

Issue:
roslaunch-complete fails if any argument has no default
When typing `roslaunch pkg  example.launch <tab>`, autocompletion should suggest the arguments of the launch file. This is implemented via function _roscomplete_launch in rosbash, which calls roslaunch-complete.

Autocompletion does not work for launch files with arguments that have no default. This causes the following exception at rosbash-complete:44:

```
/opt/ros/kinetic/lib/python2.7/dist-packages/roslaunch/arg_dump.pyc in get_args(roslaunch_files)
     47 def get_args(roslaunch_files):
     48     loader = roslaunch.xmlloader.XmlLoader(resolve_anon=False)
---> 49     config = load_config_default(roslaunch_files, None, loader=loader, verbose=False, assign_machines=False)
     50     return loader.root_context.resolve_dict.get('arg_doc', {})
     51 

/opt/ros/kinetic/lib/python2.7/dist-packages/roslaunch/config.pyc in load_config_default(roslaunch_files, port, roslaunch_strs, loader, verbose, assign_machines)
    451             loader.load(f, config, verbose=verbose)
    452         except roslaunch.xmlloader.XmlParseException as e:
--> 453             raise RLException(e)
    454         except roslaunch.loader.LoadException as e:
    455             raise RLException(e)

RLException: [/opt/ros/kinetic/share/urdf_tutorial/launch/display.launch] requires the 'model' arg to be set
``` 

This exception should be thrown when actually launching, but not during tab-completion, when the user actually tries to specify the arguments. I guess there should be a way to suppress some exception.


**Examples**

Failing:
`roslaunch urdf_tutorial display.launch <tab>`
It fails because the argument model has no default, which causes the above exception.

Working:
`roslaunch gazebo_ros range_world.launch <tab>`
It works because all arguments have defaults.


Discussion:
@dornhege, roslaunch-complete hasn't been modified since your commit in 2013. Maybe the exception handing has changed?
I would think that this never worked before. Any pull request to support this use case would be very much appreciated.

Resolving patch:
diff --git a/tools/roslaunch/src/roslaunch/arg_dump.py b/tools/roslaunch/src/roslaunch/arg_dump.py
--- a/tools/roslaunch/src/roslaunch/arg_dump.py
+++ b/tools/roslaunch/src/roslaunch/arg_dump.py
@@ -45,7 +45,7 @@
 from roslaunch.config import load_config_default
 
 def get_args(roslaunch_files):
-    loader = roslaunch.xmlloader.XmlLoader(resolve_anon=False)
+    loader = roslaunch.xmlloader.XmlLoader(resolve_anon=False, args_only=True)
     config = load_config_default(roslaunch_files, None, loader=loader, verbose=False, assign_machines=False)
     return loader.root_context.resolve_dict.get('arg_doc', {})
 
diff --git a/tools/roslaunch/src/roslaunch/xmlloader.py b/tools/roslaunch/src/roslaunch/xmlloader.py
--- a/tools/roslaunch/src/roslaunch/xmlloader.py
+++ b/tools/roslaunch/src/roslaunch/xmlloader.py
@@ -165,15 +165,18 @@ class XmlLoader(loader.Loader):
     Parser for roslaunch XML format. Loads parsed representation into ROSConfig model.
     """
 
-    def __init__(self, resolve_anon=True):
+    def __init__(self, resolve_anon=True, args_only=False):
         """
         @param resolve_anon: If True (default), will resolve $(anon foo). If
         false, will leave these args as-is.
         @type  resolve_anon: bool
+        @param args_only: if True, will only load arg tags (e.g. autocompletion purposes)
+        @type  args_only: bool
         """        
         # store the root XmlContext so that outside code can access it
         self.root_context = None
         self.resolve_anon = resolve_anon
+        self.args_only = args_only
 
     def resolve_args(self, args, context):
         """
@@ -651,7 +654,12 @@ def _recurse_load(self, ros_config, tags, context, default_machine, is_core, ver
         """
         for tag in [t for t in tags if t.nodeType == DomNode.ELEMENT_NODE]:
             name = tag.tagName
-            if name == 'group':
+            if name == 'arg':
+                self._arg_tag(tag, context, ros_config, verbose=verbose)
+            elif self.args_only:
+                # do not load other tags
+                pass
+            elif name == 'group':
                 if ifunless_test(self, tag, context):
                     self._check_attrs(tag, context, ros_config, XmlLoader.GROUP_ATTRS)
                     child_ns = self._ns_clear_params_attr(name, tag, context, ros_config)
@@ -693,8 +701,6 @@ def _recurse_load(self, ros_config, tags, context, default_machine, is_core, ver
                     default_machine = val
             elif name == 'env':
                 self._env_tag(tag, context, ros_config)
-            elif name == 'arg':
-                self._arg_tag(tag, context, ros_config, verbose=verbose)
             else:
                 ros_config.add_config_error("unrecognized tag "+tag.tagName)
         return default_machine


Test patch: