Search code examples
python-sphinxrestructuredtext

Preserve tabs in Sphinx Makefile code-block's


I use Sphinx with reStructuredText input and HTML output to document different pieces of the infrastructure. The idea is that a user who reads the documentation can copy examples and past them into her file. How can that be done for Makefile examples? Makefiles need tab characters at certain places, but Sphinx converts tabs into spaces.

Example: The command line must start with a tab in the final HTML. Here it is written with three spaces for indent and a tab:

.. code-block:: Makefile

   target: dependency
    command -i $< -o $@

code-block does not have an option to control tab expansion.

A web search with sphinx code-block makefile tab has answers for tab expansion in included code (I prefer to have it inline) or how the Sphinx Makefile can be edited, but nothing that soles my problem.


Solution

  • Tab expansion happens very early stage of parsing RST, so you have to customize parser itself to disable it. I hope this works.

    from typing import Union
    from docutils.nodes import document
    from docutils.statemachine import StringList
    from sphinx.parsers import RSTParser
    
    
    class NoTabExpansionRSTParser(RSTParser):
        def parse(self, inputstring: Union[str, StringList], document: document) -> None:
            if isinstance(inputstring, str):
                lines = inputstring.splitlines()
                inputstring = StringList(lines, document.current_source)
            super().parse(inputstring, document)
    
    
    def setup(app):
        app.add_source_parser(NoTabExpansionRSTParser, override=True)
    

    FYI, here's where tab expansion happens, which is bypassed by the above code.

    In sphinx.parsers.RSTParser#parse

    lines = docutils.statemachine.string2lines(
        inputstring, tab_width=document.settings.tab_width,
        convert_whitespace=True)
    

    In docutils.statemachine.string2lines

    return [s.expandtabs(tab_width).rstrip() for s in astring.splitlines()]