aboutsummaryrefslogtreecommitdiff
path: root/describe.py
diff options
context:
space:
mode:
authorJoe Gregorio <jcgregorio@google.com>2012-07-09 16:46:02 -0400
committerJoe Gregorio <jcgregorio@google.com>2012-07-09 16:46:02 -0400
commit81d92cc73fb5e35c1e6a539dc1eb5f60b02dd316 (patch)
treedcec60a99722c5beb7e1c777760605f134e099a4 /describe.py
parentbf14cefd46694f592da422d1977d434d09e49f3f (diff)
downloadgoogle-api-python-client-81d92cc73fb5e35c1e6a539dc1eb5f60b02dd316.tar.gz
Build cleaner and easier to read docs for dynamic surfaces.
Reviewed in http://codereview.appspot.com/6376043/.
Diffstat (limited to 'describe.py')
-rw-r--r--describe.py321
1 files changed, 303 insertions, 18 deletions
diff --git a/describe.py b/describe.py
index ff2ec1fea..8ee8e7a19 100644
--- a/describe.py
+++ b/describe.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Copyright 2007 Google Inc.
+# Copyright 2012 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,47 +14,332 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+"""Create documentation for generate API surfaces.
+
+Command-line tool that creates documentation for all APIs listed in discovery.
+The documentation is generated from a combination of the discovery document and
+the generated API surface itself.
+"""
+
__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+import json
import os
-import pydoc
import re
import sys
import httplib2
-from oauth2client.anyjson import simplejson
+from string import Template
+
from apiclient.discovery import build
+from oauth2client.anyjson import simplejson
+import uritemplate
+
BASE = 'docs/dyn'
-def document(resource, path):
- print path
+CSS = """<style>
+
+body, h1, h2, h3, div, span, p, pre, a {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
+}
+
+body {
+ font-size: 13px;
+ padding: 1em;
+}
+
+h1 {
+ font-size: 26px;
+ margin-bottom: 1em;
+}
+
+h2 {
+ font-size: 24px;
+ margin-bottom: 1em;
+}
+
+h3 {
+ font-size: 20px;
+ margin-bottom: 1em;
+ margin-top: 1em;
+}
+
+pre, code {
+ line-height: 1.5;
+ font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
+}
+
+pre {
+ margin-top: 0.5em;
+}
+
+h1, h2, h3, p {
+ font-family: Arial, sans serif;
+}
+
+h1, h2, h3 {
+ border-bottom: solid #CCC 1px;
+}
+
+.toc_element {
+ margin-top: 0.5em;
+}
+
+.firstline {
+ margin-left: 2 em;
+}
+
+.method {
+ margin-top: 1em;
+ border: solid 1px #CCC;
+ padding: 1em;
+ background: #EEE;
+}
+
+.details {
+ font-weight: bold;
+ font-size: 14px;
+}
+
+</style>
+"""
+
+DISCOVERY_URI = ('https://www.googleapis.com/discovery/v1/apis/'
+ '{api}/{apiVersion}/rest')
+
+METHOD_TEMPLATE = """<div class="method">
+ <code class="details" id="$name">$name($params)</code>
+ <pre>$doc</pre>
+</div>
+"""
+
+COLLECTION_LINK = """<p class="toc_element">
+ <code><a href="$href">$name()</a></code>
+</p>
+<p class="firstline">Returns the $name Resource.</p>
+"""
+
+METHOD_LINK = """<p class="toc_element">
+ <code><a href="#$name">$name($params)</a></code></p>
+<p class="firstline">$firstline</p>"""
+
+
+def safe_version(version):
+ """Create a safe version of the verion string.
+
+ Needed so that we can distinguish between versions
+ and sub-collections in URIs. I.e. we don't want
+ adsense_v1.1 to refer to the '1' collection in the v1
+ version of the adsense api.
+
+ Args:
+ version: string, The version string.
+ Returns:
+ The string with '.' replaced with '_'.
+ """
+
+ return version.replace('.', '_')
+
+
+def unsafe_version(version):
+ """Undoes what safe_version() does.
+
+ See safe_version() for the details.
+
+
+ Args:
+ version: string, The safe version string.
+ Returns:
+ The string with '_' replaced with '.'.
+ """
+
+ return version.replace('_', '.')
+
+
+def method_params(doc):
+ """Document the parameters of a method.
+
+ Args:
+ doc: string, The method's docstring.
+
+ Returns:
+ The method signature as a string.
+ """
+ doclines = doc.splitlines()
+ if 'Args:' in doclines:
+ begin = doclines.index('Args:')
+ if 'Returns:' in doclines[begin+1:]:
+ end = doclines.index('Returns:', begin)
+ args = doclines[begin+1: end]
+ else:
+ args = doclines[begin+1:]
+
+ parameters = []
+ for line in args:
+ m = re.search('^\s+([a-zA-Z0-9_]+): (.*)', line)
+ if m is None:
+ continue
+ pname = m.group(1)
+ desc = m.group(2)
+ if '(required)' not in desc:
+ pname = pname + '=None'
+ parameters.append(pname)
+ parameters = ', '.join(parameters)
+ else:
+ parameters = ''
+ return parameters
+
+
+def method(name, doc):
+ """Documents an individual method.
+
+ Args:
+ name: string, Name of the method.
+ doc: string, The methods docstring.
+ """
+
+ params = method_params(doc)
+ return Template(METHOD_TEMPLATE).substitute(name=name, params=params, doc=doc)
+
+
+def breadcrumbs(path, root_discovery):
+ """Create the breadcrumb trail to this page of documentation.
+
+ Args:
+ path: string, Dot separated name of the resource.
+ root_discovery: Deserialized discovery document.
+
+ Returns:
+ HTML with links to each of the parent resources of this resource.
+ """
+ parts = path.split('.')
+
+ crumbs = []
+ accumulated = []
+
+ for i, p in enumerate(parts):
+ prefix = '.'.join(accumulated)
+ # The first time through prefix will be [], so we avoid adding in a
+ # superfluous '.' to prefix.
+ if prefix:
+ prefix += '.'
+ display = p
+ if i == 0:
+ display = root_discovery.get('title', display)
+ crumbs.append('<a href="%s.html">%s</a>' % (prefix + p, display))
+ accumulated.append(p)
+
+ return ' . '.join(crumbs)
+
+
+def document_collection(resource, path, root_discovery, discovery, css=CSS):
+ """Document a single collection in an API.
+
+ Args:
+ resource: Collection or service being documented.
+ path: string, Dot separated name of the resource.
+ root_discovery: Deserialized discovery document.
+ discovery: Deserialized discovery document, but just the portion that
+ describes the resource.
+ css: string, The CSS to include in the generated file.
+ """
collections = []
+ methods = []
+ resource_name = path.split('.')[-2]
+ html = [
+ '<html><body>',
+ css,
+ '<h1>%s</h1>' % breadcrumbs(path[:-1], root_discovery),
+ '<h2>Instance Methods</h2>'
+ ]
+
+ # Which methods are for collections.
for name in dir(resource):
- if not "_" in name and callable(getattr(resource, name)) and hasattr(
- getattr(resource, name), '__is_resource__'):
- collections.append(name)
+ if not name.startswith('_') and callable(getattr(resource, name)):
+ if hasattr(getattr(resource, name), '__is_resource__'):
+ collections.append(name)
+ else:
+ methods.append(name)
+
+
+ # TOC
+ if collections:
+ for name in collections:
+ if not name.startswith('_') and callable(getattr(resource, name)):
+ href = path + name + '.html'
+ html.append(Template(COLLECTION_LINK).substitute(href=href, name=name))
+
+ if methods:
+ for name in methods:
+ if not name.startswith('_') and callable(getattr(resource, name)):
+ doc = getattr(resource, name).__doc__
+ params = method_params(doc)
+ firstline = doc.splitlines()[0]
+ html.append(Template(METHOD_LINK).substitute(
+ name=name, params=params, firstline=firstline))
+
+ if methods:
+ html.append('<h3>Method Details</h3>')
+ for name in methods:
+ dname = name.rsplit('_')[0]
+ html.append(method(name, getattr(resource, name).__doc__))
+
+ html.append('</body></html>')
- obj, name = pydoc.resolve(type(resource))
- page = pydoc.html.page(
- pydoc.describe(obj), pydoc.html.document(obj, name))
+ return '\n'.join(html)
- for name in collections:
- page = re.sub('strong>(%s)<' % name, r'strong><a href="%s">\1</a><' % (path + name + ".html"), page)
- for name in collections:
- document(getattr(resource, name)(), path + name + ".")
+
+def document_collection_recursive(resource, path, root_discovery, discovery):
+
+ html = document_collection(resource, path, root_discovery, discovery)
f = open(os.path.join(BASE, path + 'html'), 'w')
- f.write(page)
+ f.write(html)
f.close()
+ for name in dir(resource):
+ if (not name.startswith('_')
+ and callable(getattr(resource, name))
+ and hasattr(getattr(resource, name), '__is_resource__')):
+ dname = name.rsplit('_')[0]
+ collection = getattr(resource, name)()
+ document_collection_recursive(collection, path + name + '.', root_discovery,
+ discovery['resources'].get(dname, {}))
+
def document_api(name, version):
+ """Document the given API.
+
+ Args:
+ name: string, Name of the API.
+ version: string, Version of the API.
+ """
service = build(name, version)
- document(service, '%s.%s.' % (name, version))
+ response, content = http.request(
+ uritemplate.expand(
+ DISCOVERY_URI, {
+ 'api': name,
+ 'apiVersion': version})
+ )
+ discovery = json.loads(content)
+
+ version = safe_version(version)
+
+ document_collection_recursive(
+ service, '%s_%s.' % (name, version), discovery, discovery)
+
if __name__ == '__main__':
http = httplib2.Http()
- resp, content = http.request('https://www.googleapis.com/discovery/v0.3/directory?preferred=true')
+ resp, content = http.request(
+ 'https://www.googleapis.com/discovery/v1/apis?preferred=true')
if resp.status == 200:
directory = simplejson.loads(content)['items']
for api in directory: