Source code for sphinx_ros.directives

"""
``sphinx_ros.directives`` module
================================


"""

import re
import sphinx

from docutils import nodes
from docutils.parsers.rst import directives, Directive
from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.util.docfields import Field, TypedField

from .util import name_to_key, split_pkg_object


ros_sig_re = re.compile(
    r'''^((?:[^\.]*\.)*?)         # package name
         (?:(msg|srv|action)\.)?  # object type
         (\w+) \s*?$              # thing name
     ''', re.VERBOSE)


# This override allows our inline type specifiers to behave like :class: link
# when it comes to handling "." and "~" prefixes.
class RosXRefMixin(object):
    def make_xref(self, rolename, domain, target, innernode=nodes.emphasis,
                  contnode=None, env=None):
        if sphinx.version_info[:2] >= (1, 5):
            result = super(RosXRefMixin, self).make_xref(rolename, domain,
                                                         target, innernode,
                                                         contnode, env)
        else:
            result = super(RosXRefMixin, self).make_xref(rolename, domain,
                                                         target, innernode,
                                                         contnode)
        result['refspecific'] = True
        if target.startswith(('.', '~')):
            prefix, result['reftarget'] = target[0], target[1:]
            if prefix == '.':
                text = target[1:]
            elif prefix == '~':
                # text = target.split('.')[-1]
                text = re.split(r'[\./]', target)[-1]
            for node in result.traverse(nodes.Text):
                node.parent[node.parent.index(node)] = nodes.Text(text)
                break
        return result


[docs]class RosObject(ObjectDescription): """ Description of a general ROS object. """ option_spec = { 'noindex': directives.flag, 'deprecated': directives.flag }
[docs] def get_signature_prefix(self, sig): """ Return a prefix to put before the object name in the signature. """ return self.objtype + ' '
[docs] def get_object_type_prefix(self): """ May return an optional name prefix that defines object type, e.g. 'msg' or 'srv'. """ return ''
[docs] def handle_signature(self, sig, signode): """ Transform a ROS signature into rST nodes. Return (fully qualified name of the thing, package name if any). If inside a package, the current package name is handled intelligently: * it is stripped from the displayed name if present * it is added to the full name (return value) if not present """ pkg, name = split_pkg_object(sig, self.get_object_type_prefix()) env_pkg = self.options.get('package', self.env.ref_context.get('ros:package')) if not pkg == env_pkg: # TODO: issue warning that object is in wrong package pass pkg_name = pkg and pkg or env_pkg name_prefix = '.'.join([pkg_name, self.get_object_type_prefix(), '']) fullname = name_prefix + name signode['package'] = pkg_name signode['fullname'] = fullname sig_prefix = self.get_signature_prefix(sig) signode += addnodes.desc_annotation(sig_prefix, sig_prefix) if self.env.config.ros_add_package_names: signode += addnodes.desc_addname(pkg_name + '/', pkg_name + '/') signode += addnodes.desc_name(name, name) return fullname, name_prefix, self.objtype, name
[docs] def get_index_text(self, pkgname, name): """ Return the text for the index entry of the object. """ fullname = name[0] name_prefix = name[1] short_name = name[3] if pkgname: text = '{} ({} in package {})'.format(short_name, self.objtype, pkgname) else: text = '{} ({})'.format(fullname, self.objtype) return text
[docs] def add_object_to_domain_data(self, fullname, obj_type): """ Add the object to the object lists of the ROS domain data. """ objects = self.env.domaindata['ros']['objects'] if fullname in objects: self.state_machine.reporter.warning( 'duplicate object description of %s, ' % fullname + 'other instance in ' + self.env.doc2path(objects[fullname][0]) + ', use :noindex: for one of them', line=self.lineno) objects[fullname] = (self.env.docname, self.objtype)
def add_target_and_index(self, name, sig, signode): pkgname = self.options.get('package', self.env.ref_context.get('ros:package')) fullname = name[0] obj_type = name[2] short_name = name[3] # Note target if fullname not in self.state.document.ids: signode['names'].append(fullname) signode['ids'].append(fullname) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) self.add_object_to_domain_data(fullname, obj_type) indextext = self.get_index_text(pkgname, name) if sphinx.version_info[:2] >= (1, 4): entry = [('single', indextext, short_name, '', name_to_key(short_name[0]))] else: entry = [('single', indextext, short_name, '')] if indextext: self.indexnode['entries'] += entry
class RosField(RosXRefMixin, Field): pass class RosTypedField(RosXRefMixin, TypedField): pass
[docs]class RosType(RosObject): """ Super class for messages, services, and actions. #TODO A lot of methods should be moved to RosObject, to simplify. RegEx in RosObject is not needed. """ pass
[docs]class RosCurrentPackageDirective(Directive): """ This directive is just to tell Sphinx that we're documenting stuff in this package, but links to this package will not lead here. """ has_content = False required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False option_spec = {} def run(self): env = self.state.document.settings.env pkgname = self.arguments[0].strip() if pkgname == 'None': env.ref_context.pop('ros:package', None) else: env.ref_context['ros:package'] = pkgname return []
[docs]class RosPackageDirective(Directive): """ Directive to mark description of a new package. """ has_content = False required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False option_spec = { 'noindex': directives.flag, 'deprecated': directives.flag } def run(self): env = self.state.document.settings.env pkgname = self.arguments[0].strip() noindex = 'noindex' in self.options env.ref_context['ros:package'] = pkgname ret = [] if not noindex: ros_domain = env.get_domain('ros') anchor = ros_domain.add_package(pkgname, 'deprecated' in self.options) targetnode = nodes.target('', '', ids=[anchor]) self.state.document.note_explicit_target(targetnode) ret.append(targetnode) indextext = '{} (package)'.format(pkgname) if sphinx.version_info[:2] >= (1, 4): entry = ('single', indextext, anchor, '', name_to_key(pkgname)) else: entry = ('single', indextext, anchor, '') inode = addnodes.index(entries=[entry]) ret.append(inode) return ret
[docs]class RosActionDirective(RosType): """ Description of a ROS action type. """ doc_field_types = [ RosTypedField('goal_parameter', label='Goal parameters', names=('goal_param',), typerolename='obj', typenames=('goal_paramtype',), can_collapse=True), RosTypedField('result_parameter', label='Result parameters', names=('result_param',), typerolename='obj', typenames=('result_paramtype',), can_collapse=True), RosTypedField('feedback_parameter', label='Feedback parameters', names=('feedback_param',), typerolename='obj', typenames=('feedback_paramtype',), can_collapse=True) ] def get_object_type_prefix(self): return 'action'
[docs]class RosServiceDirective(RosType): """ Description of a ROS service type. """ doc_field_types = [ RosTypedField('req_parameter', label='Request parameters', names=('req_param',), typerolename='obj', typenames=('req_paramtype',), can_collapse=True), RosTypedField('resp_parameter', label='Response parameters', names=('resp_param',), typerolename='obj', typenames=('resp_paramtype',), can_collapse=True) ] def get_object_type_prefix(self): return 'srv'
[docs]class RosMessageDirective(RosType): """ Description of a ROS message type. """ doc_field_types = [ RosTypedField('parameter', label='Parameters', names=('msg_param',), typerolename='obj', typenames=('msg_paramtype',), can_collapse=True), ] def get_object_type_prefix(self): return 'msg' def add_object_to_domain_data(self, fullname, obj_type): super(RosMessageDirective, self).add_object_to_domain_data(fullname, obj_type) ros_domain = self.env.get_domain('ros') ros_domain.add_message(fullname, 'deprecated' in self.options)
[docs]class RosNodeDirective(RosObject): """ Description of a ROS node. """ doc_field_types = [ RosTypedField('publisher', label='Publishers', names=('publisher',), typerolename='obj', typenames=('publisher_msg_type',), can_collapse=True), RosTypedField('subscriber', label='Subscribers', names=('subscriber',), typerolename='obj', typenames=('subscriber_msg_type',), can_collapse=True), RosTypedField('service', label='Services', names=('service',), typerolename='obj', typenames=('service_type',), can_collapse=True) ] def get_object_type_prefix(self): return 'node' def add_object_to_domain_data(self, fullname, obj_type): super(RosNodeDirective, self).add_object_to_domain_data(fullname, obj_type) ros_domain = self.env.get_domain('ros') ros_domain.add_node(fullname, 'deprecated' in self.options)