Commits

Hong Minhee  committed 23a250a

Implemented `autoflask` directive. (`sphinxcontrib.autohttp.flask`)

  • Participants
  • Parent commits 97f6a65

Comments (0)

Files changed (7)

File httpdomain/doc/autoflask_sampleapp.py

+from flask import *
+
+
+app = Flask(__name__)
+
+
+@app.route('/')
+def home():
+    """Home page."""
+    return 'home'
+
+
+@app.route('/<user>')
+def user(user):
+    """User profile page.
+
+    :param user: user login name
+    :status 200: when user exists
+    :status 404: when user doesn't exist
+
+    """
+    return 'hi, ' + user
+
+
+@app.route('/<user>/posts/<int:post_id>')
+def post(user, post_id):
+    """User's post.
+
+    :param user: user login name
+    :param post_id: post unique id
+    :status 200: when user and post exists
+    :status 404: when user and post doesn't exist
+
+    """
+    return str(post_id), 'by', user
+
+

File httpdomain/doc/conf.py

 # 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('..'))
+sys.path.insert(0, os.path.abspath('.'))
 
 # -- General configuration -----------------------------------------------------
 
 
 # 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.httpdomain']
+extensions = ['sphinxcontrib.httpdomain', 'sphinxcontrib.autohttp.flask']
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']

File httpdomain/doc/index.rst

 domain for describing RESTful HTTP APIs.
 
 In order to use it, add :mod:`sphinxcontrib.httpdomain` into
-:data:`extensions` list of your Sphinx configuration file (:file:`conf.py`):
+:data:`extensions` list of your Sphinx configuration file (:file:`conf.py`)::
 
-.. sourcecode:: py
-
-   extensions = ['sphinxcontrib.httpdomain']
+    extensions = ['sphinxcontrib.httpdomain']
 
 
 Basic usage
    like :mailheader:`Content-Type`.
 
 
+.. module:: sphinxcontrib.autohttp.flask
+
+:mod:`sphinxcontrib.autohttp.flask` --- Exporting API reference from Flask app
+------------------------------------------------------------------------------
+
+It generates RESTful HTTP API reference documentation from a Flask_
+application's routing table, similar to :mod:`sphinx.ext.autodoc`.
+
+In order to use it, add :mod:`sphinxcontrib.autohttp.flask` into
+:data:`extensions` list of your Sphinx configuration (:file:`conf.py`) file::
+
+    extensions = ['sphinxcontrib.autohttp.flask']
+
+For example:
+
+.. sourcecode:: rst
+
+   .. autoflask:: autoflask_sampleapp:app
+      :undoc-static:
+
+will be rendered as:
+
+    .. autoflask:: autoflask_sampleapp:app
+       :undoc-static:
+
+.. rst:directive:: .. autoflask:: module:app
+
+   Generates HTTP API references from a Flask application. It takes an
+   import name, like::
+
+       your.webapplication.module:app
+
+   Colon character (``:``) separates module path and application variable.
+   Latter part can be more complex::
+
+       your.webapplication.module:create_app(config='default.cfg')
+
+   It's useful when a Flask application is created from the factory function
+   (:func:`create_app`, in the above example).
+
+   It takes several flag options as well.
+
+   ``undoc-entrypoints``
+      Excludes specified entrypoints from generated references.
+
+      For example:
+
+      .. sourcecode:: rst
+
+         .. autoflask:: yourwebapp:app
+            :undoc-entrypoints: admin, admin_login
+
+      will exclude :func:`admin`, :func:`admin_login` view functions.
+
+   ``undoc-static``
+      Excludes a view function that serves static files, which is included
+      in Flask by default.
+
+.. _Flask: http://flask.pocoo.org/
+
+
 Author and License
 ------------------
 
-The :mod:`sphinxcontrib.httpdomain`, a part of :mod:`sphinxcontrib`, is
-written by `Hong Minhee`_ and distributed under BSD license.
+The :mod:`sphinxcontrib.httpdomain` and :mod:`sphinxcontrib.autohttp`,
+parts of :mod:`sphinxcontrib`, are written by `Hong Minhee`_ and distributed
+under BSD license.
 
 .. _Hong Minhee: http://dahlia.kr/
 

File httpdomain/sphinxcontrib/.httpdomain.py.swp

Binary file removed.

File httpdomain/sphinxcontrib/autohttp/__init__.py

+"""
+    sphinxcontrib.autohttp
+    ~~~~~~~~~~~~~~~~~~~~~~
+
+    The sphinx.ext.autodoc-style HTTP API reference builder
+    for sphinxcontrib.httpdomain.
+
+    :copyright: Copyright 2011 by Hong Minhee
+    :license: BSD, see LICENSE for details.
+
+"""
+

File httpdomain/sphinxcontrib/autohttp/flask.py

+"""
+    sphinxcontrib.autohttp.flask
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    The sphinx.ext.autodoc-style HTTP API reference builder (from Flask)
+    for sphinxcontrib.httpdomain.
+
+    :copyright: Copyright 2011 by Hong Minhee
+    :license: BSD, see LICENSE for details.
+
+"""
+
+import re
+try:
+    import cStringIO as StringIO
+except ImportError:
+    import StringIO
+
+from docutils import nodes
+from docutils.statemachine import ViewList
+
+from sphinx.util.compat import Directive
+from sphinx.util.nodes import nested_parse_with_titles
+from sphinx.util.docstrings import prepare_docstring
+
+from sphinxcontrib import httpdomain
+
+
+def import_object(import_name):
+    module_name, expr = import_name.split(':', 1)
+    mod = __import__(module_name)
+    mod = reduce(getattr, module_name.split('.')[1:], mod)
+    return eval(expr, __builtins__, mod.__dict__)
+
+
+def http_directive(method, path, content):
+    method = method.lower().strip()
+    if isinstance(content, basestring):
+        content = content.splitlines()
+    yield ''
+    yield '.. http:{method}:: {path}'.format(**locals())
+    yield ''
+    for line in content:
+        yield '   ' + line
+    yield ''
+
+
+def translate_werkzeug_rule(rule):
+    from werkzeug.routing import parse_rule
+    buf = StringIO.StringIO()
+    for conv, arg, var in parse_rule(rule):
+        if conv:
+            buf.write('(')
+            if conv != 'default':
+                buf.write(conv)
+                buf.write(':')
+            buf.write(var)
+            buf.write(')')
+        else:
+            buf.write(var)
+    return buf.getvalue()
+
+
+def get_routes(app):
+    for rule in app.url_map.iter_rules():
+        methods = rule.methods.difference(['OPTIONS', 'HEAD'])
+        for method in methods:
+            path = translate_werkzeug_rule(rule.rule)
+            yield method, path, rule.endpoint
+
+
+class AutoflaskDirective(Directive):
+
+    has_content = True
+    required_arguments = 1
+    option_spec = {'undoc-endpoints': str, 'undoc-static': str}
+
+    @property
+    def undoc_endpoints(self):
+        try:
+            endpoints = re.split(r'\s*,\s*', self.options['undoc-endpoints'])
+        except KeyError:
+            return frozenset()
+        return frozenset(endpoints)
+
+    def make_rst(self):
+        app = import_object(self.arguments[0])
+        for method, path, endpoint in get_routes(app):
+            if endpoint in self.undoc_endpoints:
+                continue
+            if ('undoc-static' in self.options and endpoint == 'static' and
+                path == app.static_path + '/(path:filename)'):
+                continue
+            view = app.view_functions[endpoint]
+            docstring = prepare_docstring(view.__doc__, True)
+            if not docstring:
+                continue
+            for line in http_directive(method, path, docstring):
+                yield line
+
+    def run(self):
+        node = nodes.section()
+        node.document = self.state.document
+        result = ViewList()
+        for line in self.make_rst():
+            result.append(line, '<autoflask>')
+        nested_parse_with_titles(self.state, result, node)
+        return node.children
+
+
+def setup(app):
+    if 'http' not in app.domains:
+        httpdomain.setup(app)
+    app.add_directive('autoflask', AutoflaskDirective)
+

File httpdomain/sphinxcontrib/httpdomain.py

         for method, routes in self.domain.routes.iteritems():
             for path, info in routes.iteritems():
                 letter = path.split('/', 2)
-                entries = content.setdefault('/' + letter[1], [])
+                try:
+                    first_letter = letter[1]
+                except IndexError:
+                    first_letter = letter[0]
+                entries = content.setdefault('/' + first_letter, [])
                 entries.append([
                     method.upper() + ' ' + path, 0, info[0],
                     http_resource_anchor(method, path), '', '', info[1]