Commits

Takeshi Komiya committed e7b8740

Add sphinxcontrib-webmocks

Comments (0)

Files changed (10)

+.installed.cfg
+.coverage
+.pyc
+.swp
+bin
+_build
+develop-eggs
+dist
+eggs
+parts
+.*.egg-info
+Takeshi KOMIYA <i.tkomiya@gmail.com>
+If not otherwise noted, the extensions in this package are licensed
+under the following license.
+
+Copyright (c) 2009 by the contributors (see AUTHORS file).
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+include AUTHORS
+include LICENSE
+include README
+include CHANGES.*
+webmocks extension README
+==========================
+
+This is a sphinx extension which embeds web mock-ups .
+
+source:
+
+.. code-block:: text
+
+   :Name: :text:`Input your name`
+   :Address: :text:`Input your address`
+
+   :button:`OK` :button:`Cancel`
+
+rendered:
+
+:Name: :text:`Input your name`
+:Address: :text:`Input your address`
+
+:button:`OK` :button:`Cancel`
+
+Setting
+========
+
+You can get archive file at http://bitbucket.org/tk0miya/sphinxcontrib-webmocks/
+
+Install
+--------
+
+.. code-block:: bash
+
+   > easy_install sphinxcontrib-webmocks
+
+
+Configure Sphinx
+-----------------
+
+To enable this extension, add ``sphinxcontrib.webmocks`` module to extensions 
+option at :file:`conf.py`. 
+
+.. code-block:: python
+
+   import os, sys
+
+   # Path to the folder where webmocks.py is
+   # NOTE: not needed if the package is installed in traditional way
+   # using setup.py or easy_install
+   sys.path.append(os.path.abspath('/path/to/sphinxcontrib.webmocks'))
+
+   # Enabled extensions
+   extensions = ['sphinxcontrib.webmocks']
+
+
+Roles
+======
+
+All roles create web input forms.
+
+.. describe:: :text:`...`
+
+   **text** role creates a text input form.
+   You should specify default value of form as role-text.
+   If you do want empty form, put '_' to role-text.
+
+   Examples::
+
+      Text form is here: :text:`default value`
+
+      Empty text form is here: :text:`_`
+
+.. describe:: :textarea:`...`
+
+   **textarea** role creates a textarea input form.
+
+   Examples::
+
+      Textarea form is here: :textarea:`default value`
+
+      Empty textarea form is here: :textarea:`_`
+
+.. describe:: :select:`...`
+
+   **select** role creates a select input form.
+   You should specify selection items of select-form as role-text with cammas.
+
+   Examples::
+
+      Select form is here: :select:`Item 1,Item 2,Item 3,Item 4`
+
+.. describe:: :radio:`...`
+
+   **radio** role creates set of radio buttons. 
+   You should specify selection items of radio buttons as role-text with cammas.
+
+   Examples::
+
+      Radio buttons are here: :radio:`Item 1,Item 2,Item 3,Item 4`
+
+.. describe:: :checkbox:`...`
+
+   **checkbox** role creates set of checkboxes. 
+   You should specify selection items of checkboxes as role-text with cammas.
+
+   Examples::
+
+      Checkboxes are here: :checkbox:`Item 1,Item 2,Item 3,Item 4`
+
+.. describe:: :button:`...`
+
+   **button** role creates a button.
+   You should specify label of button as role-text.
+
+   Examples::
+
+      Button is here: :button:`OK`
+
+
+Directives
+===========
+
+.. describe:: .. menulist::
+
+   **menulist** directive defines menulist of application as nested tree.
+
+   Defined menulist is used for breadcrumb list in **page** directive.
+
+   Examples::
+
+      .. menulist::
+
+         * Menu1
+            * Sub-Menu 1-1
+            * Sub-Menu 1-2
+            * Sub-Menu 1-3
+         * Menu2
+         * Menu3
+         * Menu4
+
+.. describe:: .. page:: [page_id]
+
+   **page** directive defines a page on application.
+
+   Examples::
+
+      .. page:: create_user
+
+         :UserId: :text:`_`
+         :E-mail: :text:`_`
+
+         :button:`OK` :button:`Cancel`
+
+   This directive has some options:
+
+   .. list-table::
+      :header-rows: 1
+
+      * - Name
+        - Description
+      * - breadcrumb
+        - Add breadcrumb list to page
+      * - desctable
+        - Show descriptions of forms on page
+
+   Example::
+
+      .. page:: create_user
+         :breadcrumb: Users > Create User
+         :desctable:
+
+         :UserId: :text:`_`
+         :E-mail: :text:`_`
+
+         :button:`OK` :button:`Cancel`
+
+      .. page:: create_user2
+         :breadcrumb: Users > Create User
+         :desctable:
+
+         :UserId: :text:`_ <required, description=Allows only ASCII chars>`
+         :E-mail: :text:`_ <required>`
+
+         :button:`OK` :button:`Cancel`
+
+
+Repository
+===========
+
+This code is hosted by Bitbucket.
+
+  http://bitbucket.org/tk0miya/sphinxcontrib-webmocks/
+[egg_info]
+;tag_build = dev
+;tag_date = true
+
+[aliases]
+release = egg_info -RDb ''
+# -*- coding: utf-8 -*-
+
+from setuptools import setup, find_packages
+
+long_desc = '''
+This package contains the webmocks Sphinx extension.
+
+.. _Sphinx: http://sphinx.pocoo.org/
+
+This extension enable you to create web mock-ups in your Sphinx document.
+Following code is sample::
+
+   :Name: :text:`Input your name`
+   :Address: :text:`Input your address`
+
+   :button:`Cancel` :button:`OK`
+
+See more examples and output images in http://packages.python.org/sphinxcontrib-webmocks/ .
+'''
+
+requires = ['Sphinx>=0.6']
+
+setup(
+    name='sphinxcontrib-webmocks',
+    version='0.1.0',
+    url='http://bitbucket.org/tk0miya/sphinxcontrib-webmocks',
+    download_url='http://pypi.python.org/pypi/sphinxcontrib-webmocks',
+    license='BSD',
+    author='Takeshi Komiya',
+    author_email='i.tkomiya@gmail.com',
+    description='Sphinx "webmocks" 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'],
+)

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

sphinxcontrib/webmocks.py

+# -*- coding: utf-8 -*-
+"""
+    sphinxcontrib.webmocks
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+    :copyright: Copyright 2012 by Takeshi KOMIYA
+    :license: BSD, see LICENSE for details.
+"""
+import re
+import uuid
+from collections import OrderedDict
+
+import docutils.utils
+from docutils import nodes
+from docutils.nodes import fully_normalize_name as normalize_name
+from docutils.parsers import rst
+from docutils.statemachine import ViewList
+from sphinx.util.nodes import split_explicit_title
+
+
+class MenuList(object):
+    def __init__(self):
+        self.menu = OrderedDict()
+
+    def update(self, _dict):
+        self.menu = _dict
+
+    def keys(self):
+        return self.menu.keys()
+
+    def get(self, menu):
+        return self.menu.get(menu)
+
+    def add_menu(self, menu):
+        if menu not in self.menu:
+            self.menu[menu] = []
+
+    def add_submenu(self, menu, submenu):
+        self.add_menu(menu)
+        self.menu[menu].append(submenu)
+
+
+class MenuListDirective(rst.Directive):
+    has_content = True
+
+    def run(self):
+        node = nodes.Element()
+        self.state.nested_parse(self.content, self.content_offset, node)
+        if isinstance(node[0], nodes.bullet_list):
+            bullet = node[0]['bullet']
+            parent = None
+            for line in self.content:
+                matched = re.match('^%s\s+(.*)' % re.escape(bullet), line)
+                if matched:
+                    parent = matched.group(1)
+                    menulist.add_menu(parent)
+
+                matched = re.match('^\s+%s\s+(.*)' % re.escape(bullet), line)
+                if matched:
+                    submenu = matched.group(1)
+                    menulist.add_submenu(parent, submenu)
+
+        return []
+
+
+class PageDirective(rst.Directive):
+    has_content = True
+    optional_arguments = 1
+    option_spec = {
+        'breadcrumb': rst.directives.unchanged,
+        'desctable': rst.directives.flag,
+    }
+
+    def run(self):
+        content = self.build_screen_node()
+        screen_node = self.build_menu_layout(content)
+        node_list = [screen_node]
+
+        if 'desctable' in self.options:
+            desctable_node = self.build_desctable(content)
+            if desctable_node:
+                node_list.append(desctable_node)
+
+        if self.arguments:
+            title = " ".join(self.arguments)
+            section_node = self.build_section_node(" ".join(self.arguments))
+            section_node += node_list[0]
+            node_list[0] = section_node
+
+        return node_list
+
+    def build_menu_layout(self, content):
+        selected = self.selected_menu()
+
+        # global navigation row
+        menu = menulist.keys()
+        tbody = nodes.tbody()
+
+        if menu:
+            p = nodes.paragraph()
+            p += nodes.Text(menu[0])
+            for label in menu[1:]:
+                p += nodes.Text(' / ')
+                p += nodes.Text(label)
+            entry = nodes.entry()
+            entry += p
+            row = nodes.row()
+            row += entry
+            tbody += row
+
+        # sub navigation row
+        submenu = menulist.get(selected[0])
+
+        if submenu:
+            entry = nodes.entry()
+            p = nodes.paragraph()
+            p += nodes.Text(submenu[0])
+            entry += p
+
+            for label in submenu[1:]:
+                p += nodes.Text(' / ')
+                p += nodes.Text(label)
+
+            row = nodes.row()
+            row += entry
+            tbody += row
+
+        # content body row
+        entry = nodes.entry()
+        entry += content
+        row = nodes.row()
+        row += entry
+        tbody += row
+
+        tgroup = nodes.tgroup(cols=2)
+        tgroup += nodes.colspec(colwidth=15)
+        tgroup += nodes.colspec(colwidth=85)
+        tgroup += tbody
+
+        table = nodes.table()
+        table += tgroup
+
+        return table
+
+    def build_screen_node(self):
+        container = nodes.container()
+        if self.options.get('breadcrumb'):
+            container += self.build_breadcrumb_node()
+
+        self.state.nested_parse(self.content, self.content_offset, container)
+        return container
+
+    def selected_menu(self):
+        breadcrumb = self.options.get('breadcrumb', '')
+        return re.split("\s*[,>]\s*", breadcrumb)
+
+    def build_section_node(self, title):
+        section_node = nodes.section()
+        textnodes, title_messages = self.state.inline_text(title, self.lineno)
+        titlenode = nodes.title(title, '', *textnodes)
+        name = normalize_name(titlenode.astext())
+        section_node['names'].append(name)
+        section_node += titlenode
+        section_node += title_messages
+        self.state.document.note_implicit_target(section_node, section_node)
+
+        return section_node
+
+    def build_breadcrumb_node(self):
+        breadcrumb_node = nodes.paragraph()
+        breadcrumb = self.selected_menu()
+
+        ref = nodes.reference(refuri="#")
+        ref += nodes.emphasis(text=breadcrumb[0].strip())
+        breadcrumb_node += ref
+        for bread in breadcrumb[1:]:
+            breadcrumb_node += nodes.Text(" ")
+            breadcrumb_node += nodes.emphasis(text=">>")
+            breadcrumb_node += nodes.Text(" ")
+
+            ref = nodes.reference(refuri="#")
+            ref += nodes.emphasis(text=bread.strip())
+            breadcrumb_node += ref
+
+        return breadcrumb_node
+
+    def _get_desctable_attributes(self, content):
+        attrs = []
+        for node in content.traverse(element):
+            if isinstance(node.parent.parent, nodes.field_body):
+                name = node.parent.parent.parent[0][0]
+                attrs.append((name, node))
+            elif isinstance(node.parent.parent.parent, nodes.field_body):
+                name = node.parent.parent.parent.parent[0][0]
+                attrs.append((name, node))
+
+        return attrs
+
+    def build_desctable(self, content):
+        attrs = self._get_desctable_attributes(content)
+        if not attrs:
+            return None
+
+        # header row
+        thead = nodes.thead()
+        row = nodes.row()
+        thead += row
+        for label in ['No', 'Name', 'Type', 'Required', 'Description']:
+            p = nodes.paragraph()
+            p += nodes.Text(label)
+            entry = nodes.entry()
+            entry += p
+            row += entry
+
+        # desctable body
+        tbody = nodes.tbody()
+        for i, attr in enumerate(attrs):
+            name, node = attr
+
+            required = ''
+            if node.is_required():
+                required = 'o'
+
+            row = nodes.row()
+            for label in [i + 1, name, node.longname, required, node.description()]:
+                p = nodes.paragraph()
+                p += nodes.Text(label)
+                entry = nodes.entry()
+                entry += p
+                row += entry
+            tbody += row
+
+        tgroup = nodes.tgroup(cols=4)
+        tgroup += nodes.colspec(colwidth=5)  # No
+        tgroup += nodes.colspec(colwidth=20)  # Name
+        tgroup += nodes.colspec(colwidth=20)  # Type
+        tgroup += nodes.colspec(colwidth=5)  # Required
+        tgroup += nodes.colspec(colwidth=50)  # Description
+        tgroup += thead
+        tgroup += tbody
+
+        table = nodes.table()
+        table += tgroup
+
+        return table
+
+
+# define menulist on global
+menulist = MenuList()
+
+
+def webmock_roles(fn):
+    def func(name, rawtext, text, lineno, inliner, options={}, content=[]):
+        text = docutils.utils.unescape(text)
+        has_explicit, title, _options = split_explicit_title(text)
+    
+        if title in ('-', '_'):
+            title = ''
+    
+        if not has_explicit:
+            _options = ""
+    
+        node = fn()
+        node['rawtext'] = rawtext
+        node['title'] = title
+        node['options'] = _options
+    
+        return [node], []
+
+    return func
+
+
+class element(nodes.General, nodes.Element):
+    longname = ''
+
+    def type(self):
+        return self.__class__.__name__[1:]
+
+    def to_raw(self):
+        return nodes.raw(self['rawtext'], self.to_html(), format='html')
+
+    def to_html(self):
+        return ''
+
+    def is_required(self):
+        if re.search('required', self['options']):
+            return True
+        else:
+            return False
+
+    def description(self):
+        return re.sub('required(,\s*)*', '', self['options'])
+
+
+class multielement(element):
+    def description(self):
+        choices = u'選択肢: ' + self['title']
+        description = super(multielement, self).description()
+        if description:
+            return choices + ", " + description
+        else:
+            return choices
+
+
+class _button(element):
+    longname = u'ボタン'
+
+    def to_html(self):
+        return """<button>%s</button>""" % self['title']
+
+
+@webmock_roles
+def button_role():
+    return _button()
+
+
+class _text(element):
+    longname = u'テキスト'
+
+    def to_html(self):
+        return """<input type="text" value="%s" size="40" />""" % self['title']
+
+
+@webmock_roles
+def text_role():
+    return _text()
+
+
+class _textarea(element):
+    longname = u'テキスト(複数行)'
+
+    def to_html(self):
+        return """<textarea rows="5" cols="60">%s</textarea>""" % self['title']
+
+
+@webmock_roles
+def textarea_role():
+    return _textarea()
+
+
+class _select(multielement):
+    longname = u'プルダウン'
+
+    def to_html(self):
+        labels = self['title'].split(',')
+        options = ("<option>%s</option>" % l for l in labels)
+        return """<select>%s</select>""" % "".join(options)
+
+
+@webmock_roles
+def select_role():
+    return _select()
+
+
+class _radio(multielement):
+    longname = u'ラジオ'
+
+    def to_html(self):
+        _id = uuid.uuid1()
+        labels = self['title'].split(',')
+        options = ("""<input type="radio" name="%s" value="%s" />%s""" % (_id, l, l) for l in labels)
+        return "&nbsp;".join(options)
+
+
+@webmock_roles
+def radio_role():
+    return _radio()
+
+
+class _checkbox(multielement):
+    longname = u'チェックボックス'
+
+    def to_html(self):
+        labels = self['title'].split(',')
+        options = ["""<input type="checkbox" value="%s" />%s""" % (l, l) for l in labels]
+        return "&nbsp;".join(options)
+
+
+@webmock_roles
+def checkbox_role():
+    return _checkbox()
+
+
+def on_doctree_resolved(self, doctree, docname):
+    for node in doctree.traverse(element):
+        node.parent.replace(node, node.to_raw())
+
+
+def setup(app):
+    app.add_directive('menulist', MenuListDirective)
+    app.add_directive('page', PageDirective)
+
+    app.add_role('button', button_role)
+    app.add_role('text', text_role)
+    app.add_role('textarea', textarea_role)
+    app.add_role('select', select_role)
+    app.add_role('radio', radio_role)
+    app.add_role('checkbox', checkbox_role)
+    app.connect("doctree-resolved", on_doctree_resolved)
+## 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
+    # py
+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
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.