Commit d07d6ae1 authored by M Nasimul Haque's avatar M Nasimul Haque Committed by Tim Graham
Browse files

Fixed #20910 -- Added a "snippet" sphinx directive to allow prefixing a filename.

Thanks Marc Tamlyn for the suggestion.
parent e077224f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -282,6 +282,7 @@ answer newbie questions, and generally made Django that much better:
    Scot Hacker <shacker@birdhouse.org>
    dAniel hAhler
    hambaloney
    Nasimul Haque <nasim.haque@gmail.com>
    Will Hardy <django@willhardy.com.au>
    Brian Harring <ferringb@gmail.com>
    Brant Harris
+134 −0
Original line number Diff line number Diff line
@@ -5,11 +5,15 @@ import json
import os
import re

from docutils import nodes
from docutils.parsers.rst import directives

from sphinx import addnodes, __version__ as sphinx_ver
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.writers.html import SmartyPantsHTMLTranslator
from sphinx.util.console import bold
from sphinx.util.compat import Directive
from sphinx.util.nodes import set_source_info

# RE for option descriptions without a '--' prefix
simple_option_desc_re = re.compile(
@@ -53,6 +57,136 @@ def setup(app):
    app.add_directive('versionchanged', VersionDirective)
    app.add_builder(DjangoStandaloneHTMLBuilder)

    # register the snippet directive
    app.add_directive('snippet', SnippetWithFilename)
    # register a node for snippet directive so that the xml parser
    # knows how to handle the enter/exit parsing event
    app.add_node(snippet_with_filename,
                 html=(visit_snippet, depart_snippet_literal),
                 latex=(visit_snippet_latex, depart_snippet_latex),
                 man=(visit_snippet_literal, depart_snippet_literal),
                 text=(visit_snippet_literal, depart_snippet_literal),
                 texinfo=(visit_snippet_literal, depart_snippet_literal))


class snippet_with_filename(nodes.literal_block):
    """
    Subclass the literal_block to override the visit/depart event handlers
    """
    pass


def visit_snippet_literal(self, node):
    """
    default literal block handler
    """
    self.visit_literal_block(node)


def depart_snippet_literal(self, node):
    """
    default literal block handler
    """
    self.depart_literal_block(node)


def visit_snippet(self, node):
    """
    HTML document generator visit handler
    """
    lang = self.highlightlang
    linenos = node.rawsource.count('\n') >= self.highlightlinenothreshold - 1
    fname = node['filename']
    highlight_args = node.get('highlight_args', {})
    if node.has_key('language'):
        # code-block directives
        lang = node['language']
        highlight_args['force'] = True
    if node.has_key('linenos'):
        linenos = node['linenos']

    def warner(msg):
        self.builder.warn(msg, (self.builder.current_docname, node.line))

    highlighted = self.highlighter.highlight_block(node.rawsource, lang,
                                                   warn=warner,
                                                   linenos=linenos,
                                                   **highlight_args)
    starttag = self.starttag(node, 'div', suffix='',
                             CLASS='highlight-%s' % lang)
    self.body.append(starttag)
    self.body.append('<div class="snippet-filename">%s</div>\n''' % (fname,))
    self.body.append(highlighted)
    self.body.append('</div>\n')
    raise nodes.SkipNode


def visit_snippet_latex(self, node):
    """
    Latex document generator visit handler
    """
    self.verbatim = ''


def depart_snippet_latex(self, node):
    """
    Latex document generator depart handler.
    """
    code = self.verbatim.rstrip('\n')
    lang = self.hlsettingstack[-1][0]
    linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
    fname = node['filename']
    highlight_args = node.get('highlight_args', {})
    if 'language' in node:
        # code-block directives
        lang = node['language']
        highlight_args['force'] = True
    if 'linenos' in node:
        linenos = node['linenos']

    def warner(msg):
        self.builder.warn(msg, (self.curfilestack[-1], node.line))

    hlcode = self.highlighter.highlight_block(code, lang, warn=warner,
                                              linenos=linenos,
                                              **highlight_args)

    self.body.append('\n{\\colorbox[rgb]{0.9,0.9,0.9}'
                     '{\\makebox[\\textwidth][l]'
                     '{\\small\\texttt{%s}}}}\n' % (fname,))

    if self.table:
        hlcode = hlcode.replace('\\begin{Verbatim}',
                                '\\begin{OriginalVerbatim}')
        self.table.has_problematic = True
        self.table.has_verbatim = True

    hlcode = hlcode.rstrip()[:-14]  # strip \end{Verbatim}
    hlcode = hlcode.rstrip() + '\n'
    self.body.append('\n' + hlcode + '\\end{%sVerbatim}\n' %
                     (self.table and 'Original' or ''))
    self.verbatim = None


class SnippetWithFilename(Directive):
    """
    The 'snippet' directive that allows to add the filename (optional)
    of a code snippet in the document. This is modeled after CodeBlock.
    """
    has_content = True
    optional_arguments = 1
    option_spec = {'filename': directives.unchanged_required}

    def run(self):
        code = u'\n'.join(self.content)

        literal = snippet_with_filename(code, code)
        if self.arguments:
            literal['language'] = self.arguments[0]
        literal['filename'] = self.options['filename']
        set_source_info(self, literal)
        return [literal]


class VersionDirective(Directive):
    has_content = True
+3 −0
Original line number Diff line number Diff line
@@ -100,6 +100,9 @@ pre { font-size:small; background:#E0FFB8; border:1px solid #94da3a; border-widt
dt .literal, table .literal { background:none; }
#bd a.reference { text-decoration: none; }
#bd a.reference tt.literal { border-bottom: 1px #234f32 dotted; }
div.snippet-filename { color: white; background-color: #234F32; margin: 0; padding: 2px 5px; width: 100%; font-family: monospace; font-size: small; line-height: 1.3em; }
div.snippet-filename + div.highlight > pre { margin-top: 0; }
div.snippet-filename + pre { margin-top: 0; }

/* Restore colors of pygments hyperlinked code */
#bd .highlight .k a:link, #bd .highlight .k a:visited { color: #000000; text-decoration: none; border-bottom: 1px dotted #000000; }
+90 −80
Original line number Diff line number Diff line
@@ -130,7 +130,10 @@ this. For a small app like polls, this process isn't too difficult.

2. Move the ``polls`` directory into the ``django-polls`` directory.

3. Create a file ``django-polls/README.rst`` with the following contents::
3. Create a file ``django-polls/README.rst`` with the following contents:

   .. snippet::
       :filename: django-polls/README.rst

       =====
       Polls
@@ -172,8 +175,12 @@ to use your code.
5. Next we'll create a ``setup.py`` file which provides details about how to
   build and install the app.  A full explanation of this file is beyond the
   scope of this tutorial, but the `distribute docs
<http://packages.python.org/distribute/setuptools.html>`_ have a good explanation.
Create a file ``django-polls/setup.py`` with the following contents::
   <http://packages.python.org/distribute/setuptools.html>`_ have a good
   explanation. Create a file ``django-polls/setup.py`` with the following
   contents:

   .. snippet::
       :filename: django-polls/setup.py

       import os
       from setuptools import setup
@@ -220,7 +227,10 @@ Create a file ``django-polls/setup.py`` with the following contents::
   distribute docs referred to in the previous step discuss this file in more
   details. To include the templates, the ``README.rst`` and our ``LICENSE``
   file, create a file ``django-polls/MANIFEST.in`` with the following
   contents::
   contents:

   .. snippet::
       :filename: django-polls/MANIFEST.in

       include LICENSE
       include README.rst
+16 −4
Original line number Diff line number Diff line
@@ -344,7 +344,10 @@ the text of the choice and a vote tally. Each ``Choice`` is associated with a
``Question``.

These concepts are represented by simple Python classes. Edit the
:file:`polls/models.py` file so it looks like this::
:file:`polls/models.py` file so it looks like this:

.. snippet::
    :filename: polls/models.py

    from django.db import models

@@ -415,7 +418,10 @@ But first we need to tell our project that the ``polls`` app is installed.

Edit the :file:`mysite/settings.py` file again, and change the
:setting:`INSTALLED_APPS` setting to include the string ``'polls'``. So it'll
look like this::
look like this:

.. snippet::
    :filename: mysite/settings.py

    INSTALLED_APPS = (
        'django.contrib.admin',
@@ -589,7 +595,10 @@ of this object. Let's fix that by editing the ``Question`` model (in the
``polls/models.py`` file) and adding a
:meth:`~django.db.models.Model.__unicode__` method to both ``Question`` and
``Choice``. On Python 3, simply replace ``__unicode__`` by ``__str__`` in the
following example::
following example:

.. snippet::
    :filename: polls/models.py

    from django.db import models

@@ -633,7 +642,10 @@ admin.
    luck, things should Just Work for you.

Note these are normal Python methods. Let's add a custom method, just for
demonstration::
demonstration:

.. snippet::
    :filename: polls/models.py

    import datetime
    from django.utils import timezone
Loading