Commits

Andy Mikhailenko committed 6e6ffa0 Merge

Merged.

Comments (0)

Files changed (9)

 - spelling: Spelling checker using PyEnchant_
 - sadisplay: display SqlAlchemy model sadisplay_
 - blockdiag: embed block diagrams by using blockdiag_
+- requirements: declare requirements wherever you need (e.g. in test
+  docstrings), mark statuses and collect them in a single list 
 
 .. _aafigure: http://docutils.sourceforge.net/sandbox/aafigure/
 

requirements/MANIFEST.in

+include README
+include LICENSE
+include CHANGES.*
+recursive-include sphinxcontrib *.css

requirements/README

Empty file added.

requirements/setup.cfg

+[egg_info]
+tag_build = dev
+tag_date = true
+
+[aliases]
+release = egg_info -RDb ''

requirements/setup.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from setuptools import setup, find_packages
+
+long_desc = '''
+This package contains the requirements Sphinx extension.
+
+Allows declaring requirement specs wherever in the documentation (for instance,
+in docstrings of UnitTest.test_* methods) and displaying them as a single
+list.
+'''
+
+requires = ['Sphinx>=0.6']
+
+setup(
+    name='sphinxcontrib-requirements',
+    version='0.1',
+    url='http://bitbucket.org/neithere/sphinx-contrib-requirements',
+    download_url='http://pypi.python.org/pypi/sphinxcontrib-requirements',
+    license='BSD',
+    author='Andrey Mikhaylenko',
+    author_email='neithere@gmail.com',
+    description='Sphinx "requirements" extension',
+    long_description=long_desc,
+    zip_safe=False,
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Environment :: Console',
+        'Environment :: Web Environment',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Topic :: Documentation',
+        'Topic :: Utilities',
+    ],
+    platforms='any',
+    packages=find_packages(),
+    include_package_data=True,
+    install_requires=requires,
+    namespace_packages=['sphinxcontrib'],
+)

requirements/sphinxcontrib/__init__.py

+# -*- coding: utf-8 -*-
+"""
+    sphinxcontrib
+    ~~~~~~~~~~~~~
+
+    This package is a namespace package that contains all extensions
+    distributed in the ``sphinx-contrib`` distribution.
+
+    :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+__import__('pkg_resources').declare_namespace(__name__)
+

requirements/sphinxcontrib/requirements.css

+.status {
+    padding: 0.2em 0.5ex; 
+    font-size: 0.8em; 
+    color: #eee; 
+    background: #aaa;
+    border-radius: 5px;
+    text-shadow: 0 0 2px white;
+}
+.status.undecided { background: OldLace; color: BurlyWood }
+.status.todo { background: NavajoWhite; color: Coral }
+.status.done { background: LightBlue; color: SteelBlue }
+.status.tested { background: YellowGreen; text-shadow: 0 0 2px ForestGreen }
+.status.wontfix { color: gray }

requirements/sphinxcontrib/requirements.py

+# -*- coding: utf-8 -*-
+"""
+Requirement specs
+=================
+
+.. module:: sphinx_reqs
+   :synopsis: Allow requirement specs into documents.
+.. moduleauthor:: Andrey Mikhaylenko
+
+There are two additional directives when using this extension:
+
+.. rst:directive:: req
+
+   Use this directive like, for example, :rst:dir:`note`. Note that all
+   requirements get a status (this is not configurable yet). By default it is
+   ``undecided`` (see :role:`status` for details). You can define it by using
+   the ``status`` option::
+
+       .. requirement::
+          :status: todo
+
+          User can view the list of entries
+
+.. rst:directive:: reqlist
+
+   This directive is replaced by a list of all req directives in the whole
+   documentation.
+
+An extra role is also provided:
+
+.. rst:role:: status
+
+   Defines the status of a requirement.
+
+   Supported values are:
+
+   * ``undecided`` (default)
+   * ``todo``
+   * ``done``
+   * ``tested``
+   * ``wontfix``
+
+   Usage::
+
+       :status:`tested` User can log in
+       :status:`done` User can sign up
+       :status:`todo` User can haz cheezburger
+
+   Note that "done" and "tested" are intentionally made distinct.
+
+The extension is partially based on sphinx.ext.todo_.
+
+.. _sphinx.ext.todo: http://sphinx.pocoo.org/ext/todo.html
+
+"""
+import os
+from docutils.parsers.rst import roles, directives
+from docutils import nodes, utils
+from sphinx.environment import NoUri
+from sphinx.locale import _
+from sphinx.util.compat import Directive, make_admonition
+from sphinx.util.osutil import copyfile
+
+
+CSS_FILE = 'requirements.css'
+
+
+def status_role(name, rawtext, text, lineno, inliner, options=None, content=[]):
+    status = utils.unescape(text)
+    options = options or {}
+    options.setdefault('classes', [])
+    options['classes'] += ['status', status]
+    node = nodes.emphasis(rawtext, status, **options)
+    return [node], []
+
+
+class req_node(nodes.Admonition, nodes.Element): pass
+class reqlist(nodes.General, nodes.Element): pass
+
+
+class ReqlistDirective(Directive):
+
+    def run(self):
+        return [reqlist('')]
+
+
+class ReqDirective(Directive):
+
+    # this enables content in the directive
+    has_content = True
+
+    option_spec = {
+        'status': unicode,
+#        'done': directives.flag,
+#        'important': directives.flag,
+    }
+
+    def run(self):
+        env = self.state.document.settings.env
+
+
+        targetid = "req-%d" % env.new_serialno('req')
+        targetnode = nodes.target('', '', ids=[targetid])
+
+        #
+        # TODO:
+        #   * add config var to define whether the status should be displayed
+        #     (e.g. always / never / if explicitly given)
+        #
+        # insert a large garden gnome^W^W^W^W requirement status
+        status = self.options.get('status', 'undecided')
+        status_text = ':status:`'+status+'`'
+        self.content[0] = status_text +' '+ self.content[0]
+
+        # TODO: replace admonition with a paragraph
+        ad = make_admonition(req_node, self.name, [_('Requirement')], self.options,
+                             self.content, self.lineno, self.content_offset,
+                             self.block_text, self.state, self.state_machine)
+
+        ad[0].line = self.lineno
+        return [targetnode] + ad
+
+
+def process_reqs(app, doctree):
+    # collect all reqs in the environment
+    # this is not done in the directive itself because it some transformations
+    # must have already been run, e.g. substitutions
+    env = app.builder.env
+    if not hasattr(env, 'reqs_all_reqs'):
+        env.reqs_all_reqs = []
+    for node in doctree.traverse(req_node):
+        try:
+            targetnode = node.parent[node.parent.index(node) - 1]
+            if not isinstance(targetnode, nodes.target):
+                raise IndexError
+        except IndexError:
+            targetnode = None
+        env.reqs_all_reqs.append({
+            'docname': env.docname,
+            'lineno': node.line,
+            'req': node.deepcopy(),
+            'target': targetnode,
+        })
+
+def process_req_nodes(app, doctree, fromdocname):
+    # Replace all reqlist nodes with a list of the collected reqs.
+    # Augment each req with a backlink to the original location.
+    env = app.builder.env
+
+    if not hasattr(env, 'reqs_all_reqs'):
+        env.reqs_all_reqs = []
+
+    for node in doctree.traverse(reqlist):
+        content = []
+
+        # TODO: group (and maybe even filter) by docname
+
+        for req_info in env.reqs_all_reqs:
+
+            # (Recursively) resolve references in the req content
+            req_entry = req_info['req']
+            env.resolve_references(req_entry, req_info['docname'],
+                                   app.builder)
+
+            para = nodes.paragraph(classes=['req-source'])
+            # collect the first paragraph from the requirement
+            try:
+                para.extend(req_entry.children[1])
+            except IndexError:
+                para += nodes.Text(_('(empty spec)'))
+
+            # Create a reference
+            refnode = nodes.reference('', '', internal=True)
+            innernode = nodes.emphasis(u'→', u'→')
+            try:
+                refnode['refuri'] = app.builder.get_relative_uri(
+                    fromdocname, req_info['docname'])
+                refnode['refuri'] += '#' + req_info['target']['refid']
+            except NoUri:
+                # ignore if no URI can be determined, e.g. for LaTeX output
+                pass
+            refnode.append(nodes.Text(' '))
+            refnode.append(innernode)
+            para += refnode
+
+            content.append(para)
+
+        node.replace_self(content)
+
+def purge_reqs(app, env, docname):
+    if not hasattr(env, 'reqs_all_reqs'):
+        return
+    env.reqs_all_reqs = [req for req in env.reqs_all_reqs
+                          if req['docname'] != docname]
+
+def visit_req_node(self, node):
+    self.visit_admonition(node)
+
+def depart_req_node(self, node):
+    self.depart_admonition(node)
+
+def add_stylesheet(app):
+    app.add_stylesheet(CSS_FILE)
+
+def copy_stylesheet(app, exception):
+    if app.builder.name != 'html' or exception:
+        return
+    app.info('Copying requirements stylesheet... ', nonl=True)
+    dest = os.path.join(app.builder.outdir, '_static', CSS_FILE)
+    source = os.path.join(os.path.abspath(os.path.dirname(__file__)), CSS_FILE)
+    copyfile(source, dest)
+    app.info('done')
+
+def setup(app):
+    app.add_role('status', status_role)
+    app.add_node(reqlist)
+    app.add_node(req_node,
+                 html=(visit_req_node, depart_req_node),
+                 latex=(visit_req_node, depart_req_node),
+                 text=(visit_req_node, depart_req_node),
+                 man=(visit_req_node, depart_req_node),
+                 texinfo=(visit_req_node, depart_req_node))
+
+    app.add_directive('requirement', ReqDirective)
+    app.add_directive('reqlist', ReqlistDirective)
+    app.connect('doctree-read', process_reqs)
+    app.connect('doctree-resolved', process_req_nodes)
+    app.connect('env-purge-doc', purge_reqs)
+    app.connect('builder-inited', add_stylesheet)
+    app.connect('build-finished', copy_stylesheet)

requirements/tox.ini

+## configuration for tox <http://codespeak.net/tox/>
+
+## tox automates running certain tasks within virtualenvs.  The following
+## tox configuration outlines a basic setup for running unit tests and
+## building sphinx docs in separate virtual environments.  Give it a try!
+
+[tox]
+envlist=python,doc
+
+# test running
+[testenv:python]
+deps=
+    ## if you use nose for test running
+    # nose
+    ## if you use py.test for test running
+    # pytest
+commands=
+    ## run tests with py.test
+    # py.test []
+    ## run tests with nose
+    # nose
+
+[testenv:doc]
+deps=
+    sphinx
+    # add all Sphinx extensions and other dependencies required to build your docs
+commands=
+    ## test links
+    # sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees doc {envtmpdir}/linkcheck
+    ## test html output
+    # sphinx-build -W -b html -d {envtmpdir}/doctrees doc {envtmpdir}/html
+