Run a script in the context of the input_file's directory,
    return the text output formatted to be included as an rst
    literal text block.

    Arguments:

     input_file
      The name of the file being processed by cog.  Usually passed as
      cog.inFile.

     script_name
       The name of the Python script living in the same directory as
       input_file to be run.  If not using an interpreter, this can be
       a complete command line.  If using an alternate interpreter, it
       can be some other type of file. If the command line is very
       long, this can be a list of parts. They will be displayed
       with backslashes indicating the continuation from line to line,
       but will be joined with a space character into a single string
       to be executed.

     include_prefix=True
       Boolean controlling whether the :: prefix is included.

     ignore_error=False
       Boolean controlling whether errors are ignored.  If not
       ignored, the error is printed to stdout and then the command is
       run *again* with errors ignored so that the output ends up in
       the cogged file.

     trailing_newlines=True
       Boolean controlling whether the trailing newlines are added to
       the output.  If False, the output is passed to rstrip() then
       one newline is added.  If True, newlines are added to the
       output until it ends in 2.

     break_lines_at=0
       Integer indicating the length where lines should be broken and
       continued on the next line.  Defaults to 0, meaning no special
       handling should be done.

     line_break_mode='break'
       Name of mode to break lines.

         break
           Insert a hard break
         continue
           Insert a hard break with a backslash
         wrap
           Use textwrap.fill() to wrap
         wrap-no-breaks
           Use textwrap.fill() without breaking on hyphens
           or long words
         fill
           Use textwrap.fill(), maintaining whitespace prefix
           on subsequent lines
         continue
           Insert a hard break and backslash at the end of
           long lines, continuing on the next
         truncate
           Chop the line at the required width and discard the
           remainder

    adjust_python_for_version=True
      Boolean controlling whether the default `python`
      interpreter setting is changed to `python3` when
      running under python 3.

    line_cleanups=[]
      Process each output line through the cleanups and replace the
      input line with the output values. Each cleanup should be a
      callable that accepts the name of the original input file and
      the line of output produced and returns a replacement string, or
      the original input string if no changes are to be made.

def run_script(input_file, script_name,
               interpreter='python',
               include_prefix=True,
               ignore_error=False,
               trailing_newlines=True,
               break_lines_at=0,
               line_break_mode='break',
               adjust_python_for_version=True,
               line_cleanups=[],
               ):
    """Run a script in the context of the input_file's directory,
    return the text output formatted to be included as an rst
    literal text block.

    Arguments:

     input_file
      The name of the file being processed by cog.  Usually passed as
      cog.inFile.

     script_name
       The name of the Python script living in the same directory as
       input_file to be run.  If not using an interpreter, this can be
       a complete command line.  If using an alternate interpreter, it
       can be some other type of file. If the command line is very
       long, this can be a list of parts. They will be displayed
       with backslashes indicating the continuation from line to line,
       but will be joined with a space character into a single string
       to be executed.

     include_prefix=True
       Boolean controlling whether the :: prefix is included.

     ignore_error=False
       Boolean controlling whether errors are ignored.  If not
       ignored, the error is printed to stdout and then the command is
       run *again* with errors ignored so that the output ends up in
       the cogged file.

     trailing_newlines=True
       Boolean controlling whether the trailing newlines are added to
       the output.  If False, the output is passed to rstrip() then
       one newline is added.  If True, newlines are added to the
       output until it ends in 2.

     break_lines_at=0
       Integer indicating the length where lines should be broken and
       continued on the next line.  Defaults to 0, meaning no special
       handling should be done.

     line_break_mode='break'
       Name of mode to break lines.

         break
           Insert a hard break
         continue
           Insert a hard break with a backslash
         wrap
           Use textwrap.fill() to wrap
         wrap-no-breaks
           Use textwrap.fill() without breaking on hyphens
           or long words
         fill
           Use textwrap.fill(), maintaining whitespace prefix
           on subsequent lines
         continue
           Insert a hard break and backslash at the end of
           long lines, continuing on the next
         truncate
           Chop the line at the required width and discard the
           remainder

    adjust_python_for_version=True
      Boolean controlling whether the default `python`
      interpreter setting is changed to `python3` when
      running under python 3.

    line_cleanups=[]
      Process each output line through the cleanups and replace the
      input line with the output values. Each cleanup should be a
      callable that accepts the name of the original input file and
      the line of output produced and returns a replacement string, or
      the original input string if no changes are to be made.

    """
    rundir = path(input_file).dirname()
    if (adjust_python_for_version
            and interpreter == 'python'
            and sys.version_info[0] == 3):
        # Automatically switch to python3 if we're running under
        # python3 ourselves.
        interpreter = 'python3'
    cmd_list = script_name
    if isinstance(script_name, list):
        # We've been given a list, convert it to a string.
        script_name = ' '.join(cmd_list)
    if interpreter:
        cmd = '%(interpreter)s %(script_name)s' % {
            'interpreter': interpreter,
            'script_name': script_name,
        }
    else:
        cmd = script_name
    real_cmd = 'cd %(rundir)s; %(cmd)s 2>&1' % {
        'rundir': rundir,
        'cmd': cmd,
    }
    try:
        print()
        output_text = sh(real_cmd, capture=True, ignore_error=ignore_error)
        print(output_text)
    except Exception as err:
        print('*' * 50)
        print('ERROR run_script(%s) => %s' % (real_cmd, err))
        print('*' * 50)
        output_text = sh(real_cmd, capture=True, ignore_error=True)
        print(output_text)
        print('*' * 50)
        if not ignore_error:
            raise
    if include_prefix:
        response = '\n.. code-block:: none\n\n'
    else:
        response = ''

    # Start building our result list.
    lines = []

    # Add the command we ran to the result.
    if isinstance(cmd_list, list):
        # We were originally given a list, so interpret the
        # parts as already split up. Add the continuation
        # markers to the end.
        if interpreter:
            lines.append('\t$ {} {}'.format(interpreter, cmd_list[0] + ' \\'))
        else:
            lines.append('\t$ {}'.format(cmd_list[0] + ' \\'))
        lines.extend(
            l + ' \\'
            for l in cmd_list[1:-1]
        )
        lines.append(cmd_list[-1])
    else:
        raw_command_line = '\t$ %s' % cmd
        for cleanup in line_cleanups:
            raw_command_line = cleanup(input_file, raw_command_line)
        command_line = adjust_line_widths(
            [raw_command_line],
            break_lines_at - 1 if break_lines_at else 64,
            'continue',
        )
        lines.extend(command_line)

    lines.append('')  # a blank line
    lines.extend(output_text.splitlines())  # the output

    # Clean up the raw output lines.
    clean_lines = []
    for line in lines:
        for cleanup in line_cleanups:
            line = cleanup(input_file, line)
        clean_lines.append(line)
    lines = clean_lines

    # Deal with lines that might be too long
    if break_lines_at:
        lines = adjust_line_widths(lines, break_lines_at, line_break_mode)

    response += '\n\t'.join(lines)
    if trailing_newlines:
        while not response.endswith('\n\n'):
            response += '\n'
    else:
        response = response.rstrip()
        response += '\n'
    return response