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 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]
    }