Repository: ros/ros_comm

Issue:
rostopic echo of array indices fails
This is part of the root cause for https://github.com/ros-visualization/rqt_common_plugins/issues/261

When publishing an array message (on topic `/array`):

```
Header header
float32[] values
```

`rostopic echo /array/values[0]` fails, but `rostopic echo /array/values` succeeds. 

This worked on Hydro, and fails on Indigo. It looks like the underlying bug here is a change somewhere in rostopic's `_get_topic_type()` 



Discussion:
I'm able to reproduce this with the `test_pub.py` node in this package: https://github.com/trainman419/test_publishers

Please try #481 and check if it also fixes the problem you have with rqt_plot.

I'm not near my Indigo machine at the moment; I'll try to test this when I get home tonight.

Confirmed; this fixes rostopic echo for array members, and rqt_plot for the same cases, although I do get some rather ugly stacktraces whlie typing that don't happen in the hydro version: 

```
Traceback (most recent call last):
  File "/home/htpc/catkin_ws/src/rqt_common_plugins/rqt_plot/src/rqt_plot/plot_widget.py", line 208, in on_topic_edit_textChanged
    plottable, message = is_plottable(topic_name)
  File "/home/htpc/catkin_ws/src/rqt_common_plugins/rqt_plot/src/rqt_plot/plot_widget.py", line 108, in is_plottable
    fields, message = get_plot_fields(topic_name)
  File "/home/htpc/catkin_ws/src/rqt_common_plugins/rqt_plot/src/rqt_plot/plot_widget.py", line 49, in get_plot_fields
    topic_type, real_topic, _ = topic_helpers.get_topic_type(topic_name)
  File "<string>", line 421, in get_topic_type
  File "<string>", line 404, in _get_topic_type
  File "<string>", line 305, in msgevalgen
AssertionError: Topic name 'values[0' contains '[' but does not end with ']'
```

What exactly did you try when you got the stack trace? From the assertion it looks like you passed a topic name with an open bracket but without a closing one.

Yep. I got that stack while typing the full topic name.

Can you please post a reproducible example?

I used the topic entry box to interactively select the topic in question, using the `test_pub.py` node from my test_publishers package: https://github.com/trainman419/test_publishers

With the test_pub.py node running, run `rqt_plot` (no arguments), and then type the topic name in the Topic box: `/array/values[0]` - upon entering the [ and the 0, you should see errors on the console.

So rqt_plot is calling the API with an invalid topic name. Before it was silently ignored - now it prints the assertion. Do you want to modify rqt_plot to not call the API with temporary (invalid) user input? Or should I change the patch to silently ignore invalid input again?

rqt_plot is calling get_topic_type to determine what is and is not a valid topic/field name. The previous version of get_topic_type handled this by returning `None` for invalid topics, and rqt_plot then indicated that the topic was invalid.

I don't expect to modify rqt_plot so that it doesn't call get_topic_type with an invalid topic name.

I think it makes sense to wrap the call to get_topic_type in rqt_plot with a try/catch so that this error 
can be passed to the user more gracefully.


Resolving patch:
diff --git a/tools/rostopic/src/rostopic/__init__.py b/tools/rostopic/src/rostopic/__init__.py
--- a/tools/rostopic/src/rostopic/__init__.py
+++ b/tools/rostopic/src/rostopic/__init__.py
@@ -296,15 +296,65 @@ def msgevalgen(pattern):
     if not pattern or pattern == '/':
         return None
     assert pattern[0] == '/'
+    msg_attribute = pattern[1:]
+
+    # use slice arguments if present
+    array_index_or_slice_object = None
+    index = msg_attribute.find('[')
+    if index != -1:
+        if not msg_attribute.endswith(']'):
+            sys.stderr.write("Topic name '%s' contains '[' but does not end with ']'\n" % msg_attribute)
+            return None
+        index_string = msg_attribute[index + 1:-1]
+        try:
+            array_index_or_slice_object = _get_array_index_or_slice_object(index_string)
+        except AssertionError as e:
+            sys.stderr.write("Topic name '%s' contains invalid slice argument '%s': %s\n" % (msg_attribute, index_string, str(e)))
+            return None
+        msg_attribute = msg_attribute[:index]
+
     def msgeval(msg):
         # I will probably replace this with some less beautiful but more efficient
         try:
-            return _get_nested_attribute(msg, pattern[1:])
+            value = _get_nested_attribute(msg, msg_attribute)
         except AttributeError as e:
             sys.stdout.write("no field named [%s]"%pattern+"\n")
             return None
+        if array_index_or_slice_object is not None:
+            value = value[array_index_or_slice_object]
+        return value
     return msgeval
 
+def _get_array_index_or_slice_object(index_string):
+    assert index_string != '', 'empty array index'
+    index_string_parts = index_string.split(':')
+    if len(index_string_parts) == 1:
+        try:
+            array_index = int(index_string_parts[0])
+        except ValueError:
+            assert False, "non-integer array index step '%s'" % index_string_parts[0]
+        return array_index
+
+    slice_args = [None, None, None]
+    if index_string_parts[0] != '':
+        try:
+            slice_args[0] = int(index_string_parts[0])
+        except ValueError:
+            assert False, "non-integer slice start '%s'" % index_string_parts[0]
+    if index_string_parts[1] != '':
+        try:
+            slice_args[1] = int(index_string_parts[1])
+        except ValueError:
+            assert False, "non-integer slice stop '%s'" % index_string_parts[1]
+    if len(index_string_parts) > 2 and index_string_parts[2] != '':
+            try:
+                slice_args[2] = int(index_string_parts[2])
+            except ValueError:
+                assert False, "non-integer slice step '%s'" % index_string_parts[2]
+    if len(index_string_parts) > 3:
+        assert False, 'too many slice arguments'
+    return slice(*slice_args)
+
 def _get_nested_attribute(msg, nested_attributes):
     value = msg
     for attr in nested_attributes.split('/'):
@@ -338,6 +388,7 @@ def _get_topic_type(topic):
                 break
             msg = msg_class()
             nested_attributes = topic[len(t) + 1:].rstrip('/')
+            nested_attributes = nested_attributes.split('[')[0]
             if nested_attributes == '':
                 break
             try:


Test patch: