pythonpython-sphinxdocutils

How to create an custom sphinx extention that works in latex


I created a super small extention to add the role "icon" in my doc. The idea is to write down the following :

I'm a folder :icon:`fa fa-folder`

I adapted some code find online and came up with the following:

from docutils import nodes

def icon_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
    """add inline icons

    Returns 2 part tuple containing list of nodes to insert into the
    document and a list of system messages.  Both are allowed to be
    empty.

    :param name: The role name used in the document.
    :param rawtext: The entire markup snippet, with role.
    :param text: The text marked with the role.
    :param lineno: The line number where rawtext appears in the input.
    :param inliner: The inliner instance that called us.
    :param options: Directive options for customization.
    :param content: The directive content for customization.
    """
    node =nodes.reference()
    html = f"<i class=\"{text}\"></i>"
    node = nodes.raw('', html, format='html')
    return [node], []

def setup(app):
    """Install the plugin.

    :param app: Sphinx application context.
    """
    app.add_role('icon', icon_role)
    return

The problem is that the role is not taken into account in my pdf output, it remains blank. Is there extra code I should add to make it work in latex as well ?


Solution

  • Based on what I found in the sphinx-contrib organization and in particular in the clever youtube sphinx extension, I coded a custom node that will create the <i> tag for the HTML output and a bold output for the latex one. It will of course need more work to display the icon in latex but that's not relevant here.

    I leave the code here for anyone interested.

    # -*- coding: utf-8 -*-
    
    from docutils import nodes
    
    class icon(nodes.General, nodes.Element):
        """A general node class that will be used in setup"""
    
        pass
    
    def depart_icon_node(self, node):
        """Empty depart function, everything is handled in visit functions"""
    
        pass
    
    def visit_icon_node_html(self, node):
        """entry point of the html node. we simply create a <i> tag"""
    
        icon = node["icon"]
        self.body.append(f"<i class=\"{icon}\"></i>")
        
        return
        
    def visit_icon_node_latex(self, node):
        """ 
        entry point of the html node. we simply write down the text in bold. 
        See the linked repository for more complex macros and how to set them up in 'preamble'
        """
        
        icon = node["icon"]
            
        self.body.append("\\textbf{%s}" %(icon))
        
        return
            
    def visit_icon_node_unsuported(self, node):
        """raise error when the requested output is not supported"""
        
        self.builder.warn(f'unsupported output format (node skipped)')
        raise nodes.SkipNode
        
    _NODE_VISITORS = {
        'html': (visit_icon_node_html, depart_icon_node),
        'latex': (visit_icon_node_latex, depart_icon_node),
        'man': (visit_icon_node_unsuported, None), # don't need depart function because I raise an error 
        'texinfo': (visit_icon_node_unsuported, None), # same 
        'text': (visit_icon_node_unsuported, None) #same
    }
        
    
    def icon_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
        """add inline icons"""
        
        node = icon(icon=text)
    
        return [node], []
    
    def setup(app):
        """Install the plugin.
    
        :param app: Sphinx application context.
        """
    
        app.add_node(icon, **_NODE_VISITORS)
        app.add_role('icon', icon_role)
    
        return