Global include in restructured text

1.2k views Asked by At

I'm using reStructuredText for my blog/website and I want to add a global include file. I have access to and am happy to change the settings file I'm using to generate the html output, I just can't figure out the syntax for either:

  1. adding a default include file to the parser
  2. defining directive/inline-roles, etc in python with docutils in python

I tried reading the source code and the documentation and just find it a bit hard to follow. I'm hoping that I just missed something super-obvious, but I'd like to do something like the following (the first part is just what is already there -- you can see the rest of the file in the jekyll-rst plugin source (links right to it)

import sys
from docutils.core import publish_parts
from optparse import OptionParser
from docutils.frontend import OptionParser as DocutilsOptionParser
from docutils.parsers.rst import Parser

# sets up a writer that is then called to parse rst pages repeatedly
def transform(writer=None, part=None):
    p = OptionParser(add_help_option=False)

    # Collect all the command line options
    docutils_parser = DocutilsOptionParser(components=(writer, Parser()))
    for group in docutils_parser.option_groups:
        p.add_option_group(group.title, None).add_options(group.option_list)

    p.add_option('--part', default=part)

    opts, args = p.parse_args()


# ... more settings, etc

# then I just tell the parser/writer to process specified file X.rst every time
# (or alternately a python file defining more roles...but nicer if in rst)

Is there a simple way to do this? It'd be great to define a file defaults.rst and have that load each time.

EDIT: Here are some examples of what I'd like to be able to globally include (custom directives would be nice too, but I'd probably write those in code)

.. role:: raw-html(raw)
   :format: html

.. |common-substitution| replace:: apples and orange

.. |another common substitution| replace:: etc
2

There are 2 answers

3
Chris On BEST ANSWER

I'm not quite sure if I understand the question. Do you want to define a number of, for example, substitutions in some file and have these available in all your other reStructuredText files, or do you want to include some common HTML in your output files? Can you clarify your question?

If it is the former that you want to do you can use the include directive, as I outline in this answer.

Alternatively, if you want some common HTML included in the generated output, try copying and editing the template.txt file which is include in the module path/to/docutils/writers/html4css1/. You can include arbitrary HTML elements in this file and modify the layout of the HTML generated by Docutils. Neither of these methods require you to modify the Docuitls source code, which is always an advantage.

Edit: I don't think it is possible to set a flag to set an include file using Docuitls. However, if you can use Sphinx, which is based on Docuitls but has a load of extensions, then this package has a setting rst_prolog which does exactly what you need (see this answer). rst_prolog is:

A string of reStructuredText that will be included at the beginning of every source file that is read.

0
Michael Hoff On

I needed the exact same thing: A way to have some global reStructuredText files being automatically imported into every reStructuredText article without having to specify them each time by hand.

One solution to this problem is the following plugin:

import os
from pelican import signals
from pelican.readers import RstReader


class RstReaderWrapper(RstReader):
      enabled = RstReader.enabled
      file_extensions = ['rst']

      class FileInput(RstReader.FileInput):
          def __init__(self, *args, **kwargs):
              RstReader.FileInput_.__init__(self, *args, **kwargs)
              self.source = RstReaderWrapper.SourceWrapper(self.source)

      # Hook into RstReader
      RstReader.FileInput_ = RstReader.FileInput
      RstReader.FileInput = FileInput

      class SourceWrapper():
          """
              Mimics and wraps the result of a call to `open`
          """
          content_to_prepend = None

          def __init__(self, source):
              self.source = source

          def read(self):
              content = self.source.read()
              if self.content_to_prepend is not None:
                  content = "{}\n{}".format(self.content_to_prepend, content)
              return content

          def close(self):
              self.source.close()


def process_settings(pelicanobj):
      include_files = pelicanobj.settings.get('RST_GLOBAL_INCLUDES', []) or []
      base_path = pelicanobj.settings.get('PATH', ".")

      def read(fn):
          with open(os.path.join(base_path, fn), 'r') as res:
              content = res.read()
              return ".. INLCUSION FROM {}\n{}\n".format(fn, content)

      inclusion = "".join(map(read, include_files)) if include_files else None
      RstReaderWrapper.SourceWrapper.content_to_prepend = inclusion


def register():
signals.initialized.connect(process_settings)

Usage in short:

  • Create a plugin from the above code (best clone the repository from GitHub)
  • Import the plugin (adapt PLUGINS in pelicanconf.py)
  • Define the list of RST files (relative paths to project root) to include by setting the variable RST_GLOBAL_INCLUDES in pelicanconf.py

Please note that pelican and docutils are both not designed to allow this. Neither a signal is provided which provides a clean access to the raw contents of a source file before processing begins, nor is there a possibility to intercept the framework reading the file in "a normal way" (like subclassing, changing hardcoded configuration, etc). This plugin subclasses the internal class FileInput of RstReader and sets the class reference of RstReader.FileInput to the subclass. Also python file objects are emulated through SourceWrapper. Nevertheless, this approach works for me and is not cumbersome in the daily workflow.

I know this question is from 2012 but I think this answer can still be helpful to others.