| OLD | NEW | 
|---|
|  | (Empty) | 
| 1 #!/usr/bin/python |  | 
| 2 # |  | 
| 3 # Copyright 2014 Google Inc. All Rights Reserved. |  | 
| 4 # |  | 
| 5 # Licensed under the Apache License, Version 2.0 (the "License"); |  | 
| 6 # you may not use this file except in compliance with the License. |  | 
| 7 # You may obtain a copy of the License at |  | 
| 8 # |  | 
| 9 #     http://www.apache.org/licenses/LICENSE-2.0 |  | 
| 10 # |  | 
| 11 # Unless required by applicable law or agreed to in writing, software |  | 
| 12 # distributed under the License is distributed on an "AS IS" BASIS, |  | 
| 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |  | 
| 14 # See the License for the specific language governing permissions and |  | 
| 15 # limitations under the License. |  | 
| 16 |  | 
| 17 """Create documentation for generate API surfaces. |  | 
| 18 |  | 
| 19 Command-line tool that creates documentation for all APIs listed in discovery. |  | 
| 20 The documentation is generated from a combination of the discovery document and |  | 
| 21 the generated API surface itself. |  | 
| 22 """ |  | 
| 23 |  | 
| 24 __author__ = 'jcgregorio@google.com (Joe Gregorio)' |  | 
| 25 |  | 
| 26 import argparse |  | 
| 27 import json |  | 
| 28 import os |  | 
| 29 import re |  | 
| 30 import string |  | 
| 31 import sys |  | 
| 32 |  | 
| 33 from googleapiclient.discovery import DISCOVERY_URI |  | 
| 34 from googleapiclient.discovery import build |  | 
| 35 from googleapiclient.discovery import build_from_document |  | 
| 36 import httplib2 |  | 
| 37 import uritemplate |  | 
| 38 |  | 
| 39 CSS = """<style> |  | 
| 40 |  | 
| 41 body, h1, h2, h3, div, span, p, pre, a { |  | 
| 42   margin: 0; |  | 
| 43   padding: 0; |  | 
| 44   border: 0; |  | 
| 45   font-weight: inherit; |  | 
| 46   font-style: inherit; |  | 
| 47   font-size: 100%; |  | 
| 48   font-family: inherit; |  | 
| 49   vertical-align: baseline; |  | 
| 50 } |  | 
| 51 |  | 
| 52 body { |  | 
| 53   font-size: 13px; |  | 
| 54   padding: 1em; |  | 
| 55 } |  | 
| 56 |  | 
| 57 h1 { |  | 
| 58   font-size: 26px; |  | 
| 59   margin-bottom: 1em; |  | 
| 60 } |  | 
| 61 |  | 
| 62 h2 { |  | 
| 63   font-size: 24px; |  | 
| 64   margin-bottom: 1em; |  | 
| 65 } |  | 
| 66 |  | 
| 67 h3 { |  | 
| 68   font-size: 20px; |  | 
| 69   margin-bottom: 1em; |  | 
| 70   margin-top: 1em; |  | 
| 71 } |  | 
| 72 |  | 
| 73 pre, code { |  | 
| 74   line-height: 1.5; |  | 
| 75   font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Lucida C
     onsole', monospace; |  | 
| 76 } |  | 
| 77 |  | 
| 78 pre { |  | 
| 79   margin-top: 0.5em; |  | 
| 80 } |  | 
| 81 |  | 
| 82 h1, h2, h3, p { |  | 
| 83   font-family: Arial, sans serif; |  | 
| 84 } |  | 
| 85 |  | 
| 86 h1, h2, h3 { |  | 
| 87   border-bottom: solid #CCC 1px; |  | 
| 88 } |  | 
| 89 |  | 
| 90 .toc_element { |  | 
| 91   margin-top: 0.5em; |  | 
| 92 } |  | 
| 93 |  | 
| 94 .firstline { |  | 
| 95   margin-left: 2 em; |  | 
| 96 } |  | 
| 97 |  | 
| 98 .method  { |  | 
| 99   margin-top: 1em; |  | 
| 100   border: solid 1px #CCC; |  | 
| 101   padding: 1em; |  | 
| 102   background: #EEE; |  | 
| 103 } |  | 
| 104 |  | 
| 105 .details { |  | 
| 106   font-weight: bold; |  | 
| 107   font-size: 14px; |  | 
| 108 } |  | 
| 109 |  | 
| 110 </style> |  | 
| 111 """ |  | 
| 112 |  | 
| 113 METHOD_TEMPLATE = """<div class="method"> |  | 
| 114     <code class="details" id="$name">$name($params)</code> |  | 
| 115   <pre>$doc</pre> |  | 
| 116 </div> |  | 
| 117 """ |  | 
| 118 |  | 
| 119 COLLECTION_LINK = """<p class="toc_element"> |  | 
| 120   <code><a href="$href">$name()</a></code> |  | 
| 121 </p> |  | 
| 122 <p class="firstline">Returns the $name Resource.</p> |  | 
| 123 """ |  | 
| 124 |  | 
| 125 METHOD_LINK = """<p class="toc_element"> |  | 
| 126   <code><a href="#$name">$name($params)</a></code></p> |  | 
| 127 <p class="firstline">$firstline</p>""" |  | 
| 128 |  | 
| 129 BASE = 'docs/dyn' |  | 
| 130 |  | 
| 131 DIRECTORY_URI = 'https://www.googleapis.com/discovery/v1/apis?preferred=true' |  | 
| 132 |  | 
| 133 parser = argparse.ArgumentParser(description=__doc__) |  | 
| 134 |  | 
| 135 parser.add_argument('--discovery_uri_template', default=DISCOVERY_URI, |  | 
| 136                     help='URI Template for discovery.') |  | 
| 137 |  | 
| 138 parser.add_argument('--discovery_uri', default='', |  | 
| 139                     help=('URI of discovery document. If supplied then only ' |  | 
| 140                           'this API will be documented.')) |  | 
| 141 |  | 
| 142 parser.add_argument('--directory_uri', default=DIRECTORY_URI, |  | 
| 143                     help=('URI of directory document. Unused if --discovery_uri' |  | 
| 144                           ' is supplied.')) |  | 
| 145 |  | 
| 146 parser.add_argument('--dest', default=BASE, |  | 
| 147                     help='Directory name to write documents into.') |  | 
| 148 |  | 
| 149 |  | 
| 150 |  | 
| 151 def safe_version(version): |  | 
| 152   """Create a safe version of the verion string. |  | 
| 153 |  | 
| 154   Needed so that we can distinguish between versions |  | 
| 155   and sub-collections in URIs. I.e. we don't want |  | 
| 156   adsense_v1.1 to refer to the '1' collection in the v1 |  | 
| 157   version of the adsense api. |  | 
| 158 |  | 
| 159   Args: |  | 
| 160     version: string, The version string. |  | 
| 161   Returns: |  | 
| 162     The string with '.' replaced with '_'. |  | 
| 163   """ |  | 
| 164 |  | 
| 165   return version.replace('.', '_') |  | 
| 166 |  | 
| 167 |  | 
| 168 def unsafe_version(version): |  | 
| 169   """Undoes what safe_version() does. |  | 
| 170 |  | 
| 171   See safe_version() for the details. |  | 
| 172 |  | 
| 173 |  | 
| 174   Args: |  | 
| 175     version: string, The safe version string. |  | 
| 176   Returns: |  | 
| 177     The string with '_' replaced with '.'. |  | 
| 178   """ |  | 
| 179 |  | 
| 180   return version.replace('_', '.') |  | 
| 181 |  | 
| 182 |  | 
| 183 def method_params(doc): |  | 
| 184   """Document the parameters of a method. |  | 
| 185 |  | 
| 186   Args: |  | 
| 187     doc: string, The method's docstring. |  | 
| 188 |  | 
| 189   Returns: |  | 
| 190     The method signature as a string. |  | 
| 191   """ |  | 
| 192   doclines = doc.splitlines() |  | 
| 193   if 'Args:' in doclines: |  | 
| 194     begin = doclines.index('Args:') |  | 
| 195     if 'Returns:' in doclines[begin+1:]: |  | 
| 196       end = doclines.index('Returns:', begin) |  | 
| 197       args = doclines[begin+1: end] |  | 
| 198     else: |  | 
| 199       args = doclines[begin+1:] |  | 
| 200 |  | 
| 201     parameters = [] |  | 
| 202     for line in args: |  | 
| 203       m = re.search('^\s+([a-zA-Z0-9_]+): (.*)', line) |  | 
| 204       if m is None: |  | 
| 205         continue |  | 
| 206       pname = m.group(1) |  | 
| 207       desc = m.group(2) |  | 
| 208       if '(required)' not in desc: |  | 
| 209         pname = pname + '=None' |  | 
| 210       parameters.append(pname) |  | 
| 211     parameters = ', '.join(parameters) |  | 
| 212   else: |  | 
| 213     parameters = '' |  | 
| 214   return parameters |  | 
| 215 |  | 
| 216 |  | 
| 217 def method(name, doc): |  | 
| 218   """Documents an individual method. |  | 
| 219 |  | 
| 220   Args: |  | 
| 221     name: string, Name of the method. |  | 
| 222     doc: string, The methods docstring. |  | 
| 223   """ |  | 
| 224 |  | 
| 225   params = method_params(doc) |  | 
| 226   return string.Template(METHOD_TEMPLATE).substitute( |  | 
| 227       name=name, params=params, doc=doc) |  | 
| 228 |  | 
| 229 |  | 
| 230 def breadcrumbs(path, root_discovery): |  | 
| 231   """Create the breadcrumb trail to this page of documentation. |  | 
| 232 |  | 
| 233   Args: |  | 
| 234     path: string, Dot separated name of the resource. |  | 
| 235     root_discovery: Deserialized discovery document. |  | 
| 236 |  | 
| 237   Returns: |  | 
| 238     HTML with links to each of the parent resources of this resource. |  | 
| 239   """ |  | 
| 240   parts = path.split('.') |  | 
| 241 |  | 
| 242   crumbs = [] |  | 
| 243   accumulated = [] |  | 
| 244 |  | 
| 245   for i, p in enumerate(parts): |  | 
| 246     prefix = '.'.join(accumulated) |  | 
| 247     # The first time through prefix will be [], so we avoid adding in a |  | 
| 248     # superfluous '.' to prefix. |  | 
| 249     if prefix: |  | 
| 250       prefix += '.' |  | 
| 251     display = p |  | 
| 252     if i == 0: |  | 
| 253       display = root_discovery.get('title', display) |  | 
| 254     crumbs.append('<a href="%s.html">%s</a>' % (prefix + p, display)) |  | 
| 255     accumulated.append(p) |  | 
| 256 |  | 
| 257   return ' . '.join(crumbs) |  | 
| 258 |  | 
| 259 |  | 
| 260 def document_collection(resource, path, root_discovery, discovery, css=CSS): |  | 
| 261   """Document a single collection in an API. |  | 
| 262 |  | 
| 263   Args: |  | 
| 264     resource: Collection or service being documented. |  | 
| 265     path: string, Dot separated name of the resource. |  | 
| 266     root_discovery: Deserialized discovery document. |  | 
| 267     discovery: Deserialized discovery document, but just the portion that |  | 
| 268       describes the resource. |  | 
| 269     css: string, The CSS to include in the generated file. |  | 
| 270   """ |  | 
| 271   collections = [] |  | 
| 272   methods = [] |  | 
| 273   resource_name = path.split('.')[-2] |  | 
| 274   html = [ |  | 
| 275       '<html><body>', |  | 
| 276       css, |  | 
| 277       '<h1>%s</h1>' % breadcrumbs(path[:-1], root_discovery), |  | 
| 278       '<h2>Instance Methods</h2>' |  | 
| 279       ] |  | 
| 280 |  | 
| 281   # Which methods are for collections. |  | 
| 282   for name in dir(resource): |  | 
| 283     if not name.startswith('_') and callable(getattr(resource, name)): |  | 
| 284       if hasattr(getattr(resource, name), '__is_resource__'): |  | 
| 285         collections.append(name) |  | 
| 286       else: |  | 
| 287         methods.append(name) |  | 
| 288 |  | 
| 289 |  | 
| 290   # TOC |  | 
| 291   if collections: |  | 
| 292     for name in collections: |  | 
| 293       if not name.startswith('_') and callable(getattr(resource, name)): |  | 
| 294         href = path + name + '.html' |  | 
| 295         html.append(string.Template(COLLECTION_LINK).substitute( |  | 
| 296             href=href, name=name)) |  | 
| 297 |  | 
| 298   if methods: |  | 
| 299     for name in methods: |  | 
| 300       if not name.startswith('_') and callable(getattr(resource, name)): |  | 
| 301         doc = getattr(resource, name).__doc__ |  | 
| 302         params = method_params(doc) |  | 
| 303         firstline = doc.splitlines()[0] |  | 
| 304         html.append(string.Template(METHOD_LINK).substitute( |  | 
| 305             name=name, params=params, firstline=firstline)) |  | 
| 306 |  | 
| 307   if methods: |  | 
| 308     html.append('<h3>Method Details</h3>') |  | 
| 309     for name in methods: |  | 
| 310       dname = name.rsplit('_')[0] |  | 
| 311       html.append(method(name, getattr(resource, name).__doc__)) |  | 
| 312 |  | 
| 313   html.append('</body></html>') |  | 
| 314 |  | 
| 315   return '\n'.join(html) |  | 
| 316 |  | 
| 317 |  | 
| 318 def document_collection_recursive(resource, path, root_discovery, discovery): |  | 
| 319 |  | 
| 320   html = document_collection(resource, path, root_discovery, discovery) |  | 
| 321 |  | 
| 322   f = open(os.path.join(FLAGS.dest, path + 'html'), 'w') |  | 
| 323   f.write(html.encode('utf-8')) |  | 
| 324   f.close() |  | 
| 325 |  | 
| 326   for name in dir(resource): |  | 
| 327     if (not name.startswith('_') |  | 
| 328         and callable(getattr(resource, name)) |  | 
| 329         and hasattr(getattr(resource, name), '__is_resource__')): |  | 
| 330       dname = name.rsplit('_')[0] |  | 
| 331       collection = getattr(resource, name)() |  | 
| 332       document_collection_recursive(collection, path + name + '.', root_discover
     y, |  | 
| 333                discovery['resources'].get(dname, {})) |  | 
| 334 |  | 
| 335 def document_api(name, version): |  | 
| 336   """Document the given API. |  | 
| 337 |  | 
| 338   Args: |  | 
| 339     name: string, Name of the API. |  | 
| 340     version: string, Version of the API. |  | 
| 341   """ |  | 
| 342   service = build(name, version) |  | 
| 343   response, content = http.request( |  | 
| 344       uritemplate.expand( |  | 
| 345           FLAGS.discovery_uri_template, { |  | 
| 346               'api': name, |  | 
| 347               'apiVersion': version}) |  | 
| 348           ) |  | 
| 349   discovery = json.loads(content) |  | 
| 350 |  | 
| 351   version = safe_version(version) |  | 
| 352 |  | 
| 353   document_collection_recursive( |  | 
| 354       service, '%s_%s.' % (name, version), discovery, discovery) |  | 
| 355 |  | 
| 356 |  | 
| 357 def document_api_from_discovery_document(uri): |  | 
| 358   """Document the given API. |  | 
| 359 |  | 
| 360   Args: |  | 
| 361     uri: string, URI of discovery document. |  | 
| 362   """ |  | 
| 363   http = httplib2.Http() |  | 
| 364   response, content = http.request(FLAGS.discovery_uri) |  | 
| 365   discovery = json.loads(content) |  | 
| 366 |  | 
| 367   service = build_from_document(discovery) |  | 
| 368 |  | 
| 369   name = discovery['version'] |  | 
| 370   version = safe_version(discovery['version']) |  | 
| 371 |  | 
| 372   document_collection_recursive( |  | 
| 373       service, '%s_%s.' % (name, version), discovery, discovery) |  | 
| 374 |  | 
| 375 |  | 
| 376 if __name__ == '__main__': |  | 
| 377   FLAGS = parser.parse_args(sys.argv[1:]) |  | 
| 378   if FLAGS.discovery_uri: |  | 
| 379     document_api_from_discovery_document(FLAGS.discovery_uri) |  | 
| 380   else: |  | 
| 381     http = httplib2.Http() |  | 
| 382     resp, content = http.request( |  | 
| 383         FLAGS.directory_uri, |  | 
| 384         headers={'X-User-IP': '0.0.0.0'}) |  | 
| 385     if resp.status == 200: |  | 
| 386       directory = json.loads(content)['items'] |  | 
| 387       for api in directory: |  | 
| 388         document_api(api['name'], api['version']) |  | 
| 389     else: |  | 
| 390       sys.exit("Failed to load the discovery document.") |  | 
| OLD | NEW | 
|---|