docs: def traverse(proto_file):
      """
      proto_file is a FileDescriptorProto from protoc. We walk the SourceCodeInfo
      in this file, and find all the comments, and return a flattened out tree
      of all the messages and enums
      """
      def _collapse_comments(comments):
          return '\n'.join(
              [c.strip() for c in (comments["leading_comments"] + comments["trailing_comments"]).split('\n')])
  
      def _traverse(package, items, tree):
          for item_index, item in enumerate(items):
              item = convert_protodef_to_editable(item)
              if item_index in tree:
                  comments = tree[item_index]
                  if "leading_comments" in comments or "trailing_comments" in comments:
                      item.comment = _collapse_comments(comments)
                      del comments["leading_comments"]
                      del comments["trailing_comments"]
                  if item.kind is EnumDescriptorProto:
                      if 2 in comments: # value in EnumDescriptorProto
                          for k in comments[2]:
                              value_comment = comments[2][k]
                              if value_comment != {}:
                                  item.value[k].comment = _collapse_comments(value_comment)
                  elif item.kind is DescriptorProto:
                      if 2 in comments: # field in DescriptorProto
                          for k in comments[2]:
                              field_comment = comments[2][k]
                              if field_comment != {}:
                                  item.field[k].comment = _collapse_comments(field_comment)
                  elif item.kind is ServiceDescriptorProto:
                      if 2 in comments: # method in ServiceDescriptorProto
                          for k in comments[2]:
                              method_comment = comments[2][k]
                              if method_comment != {}:
                                  item.method[k].comment = _collapse_comments(method_comment)
                  else:
                      raise Exception, item.kind
  
              yield item, package
  
              if item.kind is DescriptorProto:
                  for enum in item.enum_type:
                      yield enum, package
  
                  for nested in item.nested_type:
                      nested_package = package + "." + item.name
  
                      for nested_item, np in _traverse(nested_package, [nested], tree[item_index]):
                          yield nested_item, np
  
      tree = collections.defaultdict(collections.defaultdict)
      for loc in proto_file.source_code_info.location:
          if loc.leading_comments or loc.trailing_comments:
              place = tree
              for p in loc.path:
                  if not place.has_key(p):
                      place[p] = collections.defaultdict(collections.defaultdict)
                  place = place[p]
              place["leading_comments"] = loc.leading_comments
              place["trailing_comments"] = loc.trailing_comments
      
      # Only message, services, enums, extensions, options
      if set(tree.keys()).difference(set([4, 5, 6, 7, 8])) != set():
          raise Exception, tree
  
      return {"types":
          list(itertools.chain(
              _traverse(proto_file.package, proto_file.service, tree[6]), # 6 is service_type in FileDescriptorProto
              _traverse(proto_file.package, proto_file.enum_type, tree[5]), # 5 is enum_type in FileDescriptorProto
              _traverse(proto_file.package, proto_file.message_type, tree[4]), # 4 is message_type in FileDescriptorProto
          )),
          "file": ["".join(x.leading_detached_comments) for x in proto_file.source_code_info.location if len(x.leading_detached_comments) > 0]
      }