Commits

Evgeniy Tatarkin committed c8c5c2b

add sadisplay extension

Comments (0)

Files changed (16)

 
 spelling:
    Doug Hellmann <doug.hellmann@gmail.com>
+
+sadisplay:
+   Evgeniy Tatarkin <tatarkin.evg@gmail.com>
 - omegat: support tools to collaborate with OmegaT_ (Sphinx 1.1 needed)
 - plantuml: embed UML diagram by using PlantUML_
 - spelling: Spelling checker using PyEnchant_
+- sadisplay: display SqlAlchemy model sadisplay_
 
 .. _aafigure: http://docutils.sourceforge.net/sandbox/aafigure/
 
 
 .. _PyEnchant: http://www.rfk.id.au/software/pyenchant/
 
+.. _sadisplay: http://bitbucket.org/estin/sadisplay/wiki/Home
+
+
 For contributors
 ================
 

sadisplay/MANIFEST.in

+include README
+include LICENSE
+include CHANGES.*
+recursive-include tests *
+recursive-exclude tests *.pyc
+Display SqlAlchemy models
+=========================
+
+Used sphinxcontrib-plantuml_ extension for rendering PlantUML_ diagrams
+generated by SqlALchemy models.
+
+Most part of code based on sphinxcontrib-plantuml_ source code.
+
+Install
+-------
+
+::
+
+    pip install sphinxcontrib-sadisplay
+
+
+Usage
+-----
+
+::
+
+    .. sadisplay::
+        :module: myapp.model
+
+.. _PlantUML: http://plantuml.sourceforge.net/
+.. _sphinxcontrib-plantuml: http://bitbucket.org/birkenfeld/sphinx-contrib

sadisplay/setup.cfg

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

sadisplay/setup.py

+# -*- coding: utf-8 -*-
+import os
+from setuptools import setup, find_packages
+
+here = os.path.dirname(os.path.abspath(__file__))
+
+
+requires = [
+    'Sphinx>=0.6',
+    'sadisplay',
+    'sphinxcontrib-plantuml'
+]
+
+
+setup(
+    name='sphinxcontrib-sadisplay',
+    version='0.1',
+    url='http://bitbucket.org/birkenfeld/sphinx-contrib',
+    download_url='http://pypi.python.org/pypi/sphinxcontrib-sadisplay',
+    license='BSD',
+    author='Evgeniy Tatarkin',
+    author_email='tatarkin.evg@gmail.com',
+    description='Sphinx "sadisplay" extension',
+    long_description=open(os.path.join(here, 'README')).read(),
+    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'],
+)

sadisplay/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__)
+

sadisplay/sphinxcontrib/sadisp.py

+# -*- coding: utf-8 -*-
+import sys
+from sphinx.errors import SphinxWarning
+from sphinx.util.compat import Directive
+from docutils.parsers.rst import directives
+from docutils import nodes
+
+import sadisplay
+
+
+class SaNode(nodes.General, nodes.Element):
+    pass
+
+
+class SadisplayDirective(Directive):
+    """Directive to display SQLAlchemy models
+
+    Example::
+
+        .. sadisplay::
+           :module: myapp.model
+
+    """
+    has_content = False
+    option_spec = {
+        'alt': directives.unchanged,
+        'render': directives.unchanged,
+        'module': directives.unchanged,
+        'include': directives.unchanged,
+        'exclude': directives.unchanged,
+    }
+
+    def run(self):
+        node = SaNode()
+        #node['uml'] = '\n'.join(self.content)
+        node['alt'] = self.options.get('alt', None)
+        node['render'] = self.options.get('render', u'plantuml')
+        node['module'] = self.options.get('module', u'')
+
+        def tolist(val):
+            if val:
+                return map(lambda i: i.strip(), val.split(','))
+            return []
+
+        node['include'] = tolist(self.options.get('include', None))
+        node['exclude'] = tolist(self.options.get('exclude', None))
+
+        if node['include'] and node['exclude']:
+            raise SphinxWarning(u'sadisplay directive error - \
+                    both defined :include: and :exclude:')
+
+        return [node]
+
+
+def render(self, node):
+
+    __import__(node['module'], globals(), locals(), [], -1)
+    module = sys.modules[node['module']]
+
+    all_names = [getattr(module, attr) for attr in dir(module)]
+
+    names = []
+
+    if node['exclude']:
+        for i in all_names:
+            try:
+                if i.__name__ not in node['exclude']:
+                    names.append(i)
+            except AttributeError:
+                pass
+
+    elif node['include']:
+        for i in all_names:
+            try:
+                if i.__name__ in node['include']:
+                    names.append(i)
+            except AttributeError:
+                pass
+
+    else:
+        names = all_names
+
+    desc = sadisplay.describe(names)
+
+    if node['render'] == u'plantuml':
+
+        from sphinxcontrib import plantuml
+        plantuml_node = plantuml.plantuml()
+        plantuml_node['uml'] = sadisplay.plantuml(desc)
+        plantuml_node['alt'] = node['alt']
+        return plantuml.render_plantuml(self, plantuml_node)
+
+    elif node['render'] == u'graphviz':
+        pass
+
+
+def html_visit(self, node):
+    try:
+        refname = render(self, node)
+    except Exception, err:
+        self.builder.warn(str(err))
+        raise nodes.SkipNode
+    self.body.append(self.starttag(node, 'p', CLASS='sadisplay'))
+    self.body.append('<img src="%s" alt="%s" />\n'
+                     % (self.encode(refname),
+                        self.encode(node['alt'] or node['module'])))
+    self.body.append('</p>\n')
+    raise nodes.SkipNode
+
+
+def latex_visit(self, node):
+    try:
+        refname = render(self, node)
+    except Exception, err:
+        self.builder.warn(str(err))
+        raise nodes.SkipNode
+    self.body.append('\\includegraphics{%s}' % self.encode(refname))
+    raise nodes.SkipNode
+
+
+def setup(app):
+    app.add_node(SaNode,
+                 html=(html_visit, None),
+                 latex=(latex_visit, None))
+    app.add_directive('sadisplay', SadisplayDirective)

sadisplay/tests/fakecmd.py

+#!/usr/bin/env python
+import sys
+print '%', ' '.join(sys.argv)
+for line in sys.stdin:
+    sys.stdout.write(line)

sadisplay/tests/fixture/_static/.placeholder

Empty file added.

sadisplay/tests/fixture/app/model.py

+# -*- coding: utf-8 -*-
+from sqlalchemy import Table, Column, Integer, Unicode, ForeignKey
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relation, mapper
+
+
+BASE = declarative_base()
+
+
+class User(BASE):
+    __tablename__ = 'user_table'
+
+    id = Column(Integer, primary_key=True)
+    name = Column(Unicode(50))
+
+    def login(self):
+        pass
+
+    def __repr__(self):
+        pass
+
+
+class Admin(User):
+    __tablename__ = 'admin_table'
+    __mapper_args__ = {'polymorphic_identity': 'user_table'}
+
+    id = Column(Integer, ForeignKey('user_table.id'), primary_key=True)
+    phone = Column(Unicode(50))
+
+    def permissions(self):
+        pass
+
+    def __unicode__(self):
+        pass
+
+
+class Address(BASE):
+    __tablename__ = 'address_table'
+
+    id = Column(Integer, primary_key=True)
+    user_id = Column(Integer, ForeignKey('user_table.id'))
+    user = relation(User, backref="address")
+
+
+books = Table('books', BASE.metadata,
+    Column('id', Integer, primary_key=True),
+    Column('title', Unicode(200), nullable=False),
+    Column('user_id', Integer, ForeignKey('user_table.id')),
+)
+
+
+class Book(object):
+    pass
+
+
+mapper(Book, books, {'user': relation(User, backref='books')})

sadisplay/tests/fixture/conf.py

+# -*- coding: utf-8 -*-
+#
+# plantuml_fixture documentation build configuration file, created by
+# sphinx-quickstart on Sat Nov 20 20:21:05 2010.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('app'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinxcontrib.plantuml', 'sphinxcontrib.sadisp']
+
+plantuml = 'java -jar plantuml.jar'.split()
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'plantuml_fixture'
+copyright = u'2010, Yuya Nishihara'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0'
+# The full version, including alpha/beta/rc tags.
+release = '0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'plantuml_fixturedoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'plantuml_fixture.tex', u'plantuml\\_fixture Documentation',
+   u'Yuya Nishihara', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'plantuml_fixture', u'plantuml_fixture Documentation',
+     [u'Yuya Nishihara'], 1)
+]

sadisplay/tests/fixture/index.rst

+PlantUML Example
+================
+
+Class diagram:
+
+.. uml::
+
+   Foo <|-- Bar
+
+Sequence diagram:
+
+.. uml::
+
+   Alice -> Bob: Hello!
+   Alice <- Bob: Hi!
+
+Sequence diagram in Japanese:
+
+.. uml::
+
+   花子 -> 太郎: こんにちは!
+   花子 <- 太郎: うっす!

sadisplay/tests/test_directive.py

+# -*- coding: utf-8 -*-
+from docutils.parsers.rst import Parser
+from docutils.utils import new_document
+from docutils.frontend import OptionParser
+from sphinx.application import Sphinx
+from sphinx.errors import SphinxWarning
+from sphinx.config import Config
+
+from nose.tools import raises
+
+from sphinxcontrib import sadisp
+
+
+_parser, _settings = None, []
+
+
+class FakeSphinx(Sphinx):
+    def __init__(self):
+        self.config = Config(None, None, None, None)
+
+
+def setup():
+    global _parser, _settings
+    _parser = Parser()
+    _settings = OptionParser().get_default_values()
+    _settings.tab_width = 8
+    _settings.pep_references = False
+    _settings.rfc_references = False
+    app = FakeSphinx()
+    sadisp.setup(app)
+
+
+def with_parsed(func):
+    def test():
+        doc = new_document('<test>', _settings)
+        src = '\n'.join(l[4:] for l in func.__doc__.splitlines()[2:])
+        _parser.parse(src, doc)
+        func(doc.children)
+    test.func_name = func.func_name
+    return test
+
+
+@with_parsed
+def test_simple(nodes):
+    """Simple sadisplay directive
+
+    .. sadisplay::
+        :module: myapp.model
+    """
+    n = nodes[0]
+    assert n['alt'] is None
+    assert n['render'] == 'plantuml'
+    assert n['module'] == 'myapp.model'
+    assert n['include'] == []
+    assert n['exclude'] == []
+
+
+@with_parsed
+def test_include(nodes):
+    """Test sadisplay directive
+    with include
+
+    .. sadisplay::
+        :module: myapp.model
+        :include: User, Group
+    """
+    n = nodes[0]
+    assert n['alt'] is None
+    assert n['render'] == 'plantuml'
+    assert n['module'] == 'myapp.model'
+    assert n['include'] == [u'User', u'Group']
+
+
+@raises(SphinxWarning)
+@with_parsed
+def test_warning(nodes):
+    """Test sadisplay directive
+    with include and exclude
+
+    .. sadisplay::
+        :module: myapp.model
+        :include: User, Group
+        :exclude: Permission
+    """
+    pass

sadisplay/tests/test_functional.py

+# -*- coding: utf-8 -*-
+import os
+import tempfile
+import shutil
+import glob
+from sphinx.application import Sphinx
+
+_fixturedir = os.path.join(os.path.dirname(__file__), 'fixture')
+_fakecmd = os.path.join(os.path.dirname(__file__), 'fakecmd.py')
+
+_tempdir = _srcdir = _outdir = None
+
+
+def setup():
+    global _tempdir, _srcdir, _outdir
+    _tempdir = tempfile.mkdtemp()
+    _srcdir = os.path.join(_tempdir, 'src')
+    _outdir = os.path.join(_tempdir, 'out')
+    os.mkdir(_srcdir)
+
+
+def teardown():
+    shutil.rmtree(_tempdir)
+
+
+def readfile(fname):
+    f = open(os.path.join(_outdir, fname), 'rb')
+    try:
+        return f.read()
+    finally:
+        f.close()
+
+
+def runsphinx(text, builder, confoverrides):
+    f = open(os.path.join(_srcdir, 'index.rst'), 'w')
+    try:
+        f.write(text.encode('utf-8'))
+    finally:
+        f.close()
+    app = Sphinx(_srcdir, _fixturedir, _outdir, _outdir, builder,
+                 confoverrides)
+    app.build()
+
+
+def with_runsphinx(builder, confoverrides={'plantuml': _fakecmd}):
+    def wrapfunc(func):
+        def test():
+            src = '\n'.join(l[4:] for l in func.__doc__.splitlines()[2:])
+            os.mkdir(_outdir)
+            try:
+                runsphinx(src, builder, confoverrides)
+                func()
+            finally:
+                os.unlink(os.path.join(_srcdir, 'index.rst'))
+                shutil.rmtree(_outdir)
+        test.func_name = func.func_name
+        return test
+    return wrapfunc
+
+
+@with_runsphinx('html')
+def test_buildhtml_simple():
+    """Generate simple HTML
+
+    .. sadisplay::
+        :module: model
+    """
+
+    files = glob.glob(os.path.join(_outdir, '_images', 'plantuml-*.png'))
+    assert len(files) == 1
+    assert '<img src="_images/plantuml' in readfile('index.html')
+
+    content = readfile(files[0])
+    assert u'Admin' in content
+    assert u'User' in content
+    assert u'Address' in content
+
+
+@with_runsphinx('latex')
+def test_buildlatex_simple():
+    """Generate simple LaTeX
+
+    .. sadisplay::
+       :module: model
+       :exclude: Admin
+    """
+    files = glob.glob(os.path.join(_outdir, 'plantuml-*.png'))
+    assert len(files) == 1
+    assert r'\includegraphics{plantuml-' in \
+            readfile('plantuml_fixture.tex')
+
+    content = readfile(files[0])
+    assert u'Admin' not in content
+    assert u'User' in content
+    assert u'Address' in content

sadisplay/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
+