blob: c808a3ba4142b8c6aaab33011f9e818cde632ea6 [file] [log] [blame]
Joe Gregorio79daca02013-03-29 16:25:52 -04001#!/usr/bin/python
Joe Gregorio20a5aa92011-04-01 17:44:25 -04002#
Craig Citro751b7fb2014-09-23 11:20:38 -07003# Copyright 2014 Google Inc. All Rights Reserved.
Joe Gregorio20a5aa92011-04-01 17:44:25 -04004#
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
Joe Gregorio81d92cc2012-07-09 16:46:02 -040017"""Create documentation for generate API surfaces.
18
19Command-line tool that creates documentation for all APIs listed in discovery.
20The documentation is generated from a combination of the discovery document and
21the generated API surface itself.
22"""
Christian Clauss9fdc2b22019-07-22 19:43:21 +020023from __future__ import print_function
Joe Gregorio81d92cc2012-07-09 16:46:02 -040024
Bu Sun Kim66bb32c2019-10-30 10:11:58 -070025__author__ = "jcgregorio@google.com (Joe Gregorio)"
Joe Gregorio20a5aa92011-04-01 17:44:25 -040026
Bu Sun Kimc9773042019-07-17 14:03:17 -070027from collections import OrderedDict
Joe Gregorio79daca02013-03-29 16:25:52 -040028import argparse
Bu Sun Kimc9773042019-07-17 14:03:17 -070029import collections
Craig Citro6ae34d72014-08-18 23:10:09 -070030import json
Joe Gregorioafc45f22011-02-20 16:11:28 -050031import os
Joe Gregorioafc45f22011-02-20 16:11:28 -050032import re
Joe Gregorio79daca02013-03-29 16:25:52 -040033import string
Joe Gregorio20a5aa92011-04-01 17:44:25 -040034import sys
Joe Gregorioafc45f22011-02-20 16:11:28 -050035
John Asmuth864311d2014-04-24 15:46:08 -040036from googleapiclient.discovery import DISCOVERY_URI
37from googleapiclient.discovery import build
38from googleapiclient.discovery import build_from_document
Jon Wayne Parrottfd2f99c2016-02-19 16:02:04 -080039from googleapiclient.discovery import UnknownApiNameOrVersion
Igor Maravić22435292017-01-19 22:28:22 +010040from googleapiclient.http import build_http
Joe Gregorio81d92cc2012-07-09 16:46:02 -040041import uritemplate
42
Joe Gregorio81d92cc2012-07-09 16:46:02 -040043CSS = """<style>
44
45body, h1, h2, h3, div, span, p, pre, a {
46 margin: 0;
47 padding: 0;
48 border: 0;
49 font-weight: inherit;
50 font-style: inherit;
51 font-size: 100%;
52 font-family: inherit;
53 vertical-align: baseline;
54}
55
56body {
57 font-size: 13px;
58 padding: 1em;
59}
60
61h1 {
62 font-size: 26px;
63 margin-bottom: 1em;
64}
65
66h2 {
67 font-size: 24px;
68 margin-bottom: 1em;
69}
70
71h3 {
72 font-size: 20px;
73 margin-bottom: 1em;
74 margin-top: 1em;
75}
76
77pre, code {
78 line-height: 1.5;
79 font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
80}
81
82pre {
83 margin-top: 0.5em;
84}
85
86h1, h2, h3, p {
87 font-family: Arial, sans serif;
88}
89
90h1, h2, h3 {
91 border-bottom: solid #CCC 1px;
92}
93
94.toc_element {
95 margin-top: 0.5em;
96}
97
98.firstline {
99 margin-left: 2 em;
100}
101
102.method {
103 margin-top: 1em;
104 border: solid 1px #CCC;
105 padding: 1em;
106 background: #EEE;
107}
108
109.details {
110 font-weight: bold;
111 font-size: 14px;
112}
113
114</style>
115"""
116
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400117METHOD_TEMPLATE = """<div class="method">
118 <code class="details" id="$name">$name($params)</code>
119 <pre>$doc</pre>
120</div>
121"""
122
123COLLECTION_LINK = """<p class="toc_element">
124 <code><a href="$href">$name()</a></code>
125</p>
126<p class="firstline">Returns the $name Resource.</p>
127"""
128
129METHOD_LINK = """<p class="toc_element">
130 <code><a href="#$name">$name($params)</a></code></p>
131<p class="firstline">$firstline</p>"""
132
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700133BASE = "docs/dyn"
Joe Gregoriobb964352013-03-03 20:45:29 -0500134
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700135DIRECTORY_URI = "https://www.googleapis.com/discovery/v1/apis"
Joe Gregoriobb964352013-03-03 20:45:29 -0500136
Joe Gregorio79daca02013-03-29 16:25:52 -0400137parser = argparse.ArgumentParser(description=__doc__)
Joe Gregoriobb964352013-03-03 20:45:29 -0500138
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700139parser.add_argument(
140 "--discovery_uri_template",
141 default=DISCOVERY_URI,
142 help="URI Template for discovery.",
143)
Joe Gregoriobb964352013-03-03 20:45:29 -0500144
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700145parser.add_argument(
146 "--discovery_uri",
147 default="",
148 help=(
149 "URI of discovery document. If supplied then only "
150 "this API will be documented."
151 ),
152)
Joe Gregoriobb964352013-03-03 20:45:29 -0500153
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700154parser.add_argument(
155 "--directory_uri",
156 default=DIRECTORY_URI,
157 help=("URI of directory document. Unused if --discovery_uri" " is supplied."),
158)
Joe Gregoriobb964352013-03-03 20:45:29 -0500159
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700160parser.add_argument(
161 "--dest", default=BASE, help="Directory name to write documents into."
162)
Joe Gregoriobb964352013-03-03 20:45:29 -0500163
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400164
165def safe_version(version):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700166 """Create a safe version of the verion string.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400167
168 Needed so that we can distinguish between versions
169 and sub-collections in URIs. I.e. we don't want
170 adsense_v1.1 to refer to the '1' collection in the v1
171 version of the adsense api.
172
173 Args:
174 version: string, The version string.
175 Returns:
176 The string with '.' replaced with '_'.
177 """
178
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700179 return version.replace(".", "_")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400180
181
182def unsafe_version(version):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700183 """Undoes what safe_version() does.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400184
185 See safe_version() for the details.
186
187
188 Args:
189 version: string, The safe version string.
190 Returns:
191 The string with '_' replaced with '.'.
192 """
193
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700194 return version.replace("_", ".")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400195
196
197def method_params(doc):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700198 """Document the parameters of a method.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400199
200 Args:
201 doc: string, The method's docstring.
202
203 Returns:
204 The method signature as a string.
205 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700206 doclines = doc.splitlines()
207 if "Args:" in doclines:
208 begin = doclines.index("Args:")
209 if "Returns:" in doclines[begin + 1 :]:
210 end = doclines.index("Returns:", begin)
211 args = doclines[begin + 1 : end]
212 else:
213 args = doclines[begin + 1 :]
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400214
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700215 parameters = []
216 pname = None
217 desc = ""
218
219 def add_param(pname, desc):
220 if pname is None:
221 return
222 if "(required)" not in desc:
223 pname = pname + "=None"
224 parameters.append(pname)
225
226 for line in args:
227 m = re.search("^\s+([a-zA-Z0-9_]+): (.*)", line)
228 if m is None:
229 desc += line
230 continue
231 add_param(pname, desc)
232 pname = m.group(1)
233 desc = m.group(2)
234 add_param(pname, desc)
235 parameters = ", ".join(parameters)
236 else:
237 parameters = ""
238 return parameters
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400239
240
241def method(name, doc):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700242 """Documents an individual method.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400243
244 Args:
245 name: string, Name of the method.
246 doc: string, The methods docstring.
247 """
248
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700249 params = method_params(doc)
Billy SU45cace82020-04-22 03:46:31 +0800250 if sys.version_info.major >= 3:
251 import html
252 doc = html.escape(doc)
253 else:
254 import cgi
255 doc = cgi.escape(doc)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700256 return string.Template(METHOD_TEMPLATE).substitute(
257 name=name, params=params, doc=doc
258 )
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400259
260
261def breadcrumbs(path, root_discovery):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700262 """Create the breadcrumb trail to this page of documentation.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400263
264 Args:
265 path: string, Dot separated name of the resource.
266 root_discovery: Deserialized discovery document.
267
268 Returns:
269 HTML with links to each of the parent resources of this resource.
270 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700271 parts = path.split(".")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400272
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700273 crumbs = []
274 accumulated = []
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400275
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700276 for i, p in enumerate(parts):
277 prefix = ".".join(accumulated)
278 # The first time through prefix will be [], so we avoid adding in a
279 # superfluous '.' to prefix.
280 if prefix:
281 prefix += "."
282 display = p
283 if i == 0:
284 display = root_discovery.get("title", display)
285 crumbs.append('<a href="%s.html">%s</a>' % (prefix + p, display))
286 accumulated.append(p)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400287
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700288 return " . ".join(crumbs)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400289
290
291def document_collection(resource, path, root_discovery, discovery, css=CSS):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700292 """Document a single collection in an API.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400293
294 Args:
295 resource: Collection or service being documented.
296 path: string, Dot separated name of the resource.
297 root_discovery: Deserialized discovery document.
298 discovery: Deserialized discovery document, but just the portion that
299 describes the resource.
300 css: string, The CSS to include in the generated file.
301 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700302 collections = []
303 methods = []
304 resource_name = path.split(".")[-2]
305 html = [
306 "<html><body>",
307 css,
308 "<h1>%s</h1>" % breadcrumbs(path[:-1], root_discovery),
309 "<h2>Instance Methods</h2>",
310 ]
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400311
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700312 # Which methods are for collections.
313 for name in dir(resource):
314 if not name.startswith("_") and callable(getattr(resource, name)):
315 if hasattr(getattr(resource, name), "__is_resource__"):
316 collections.append(name)
317 else:
318 methods.append(name)
Joe Gregorioafc45f22011-02-20 16:11:28 -0500319
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700320 # TOC
321 if collections:
322 for name in collections:
323 if not name.startswith("_") and callable(getattr(resource, name)):
324 href = path + name + ".html"
325 html.append(
326 string.Template(COLLECTION_LINK).substitute(href=href, name=name)
327 )
Joe Gregorioafc45f22011-02-20 16:11:28 -0500328
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700329 if methods:
330 for name in methods:
331 if not name.startswith("_") and callable(getattr(resource, name)):
332 doc = getattr(resource, name).__doc__
333 params = method_params(doc)
334 firstline = doc.splitlines()[0]
335 html.append(
336 string.Template(METHOD_LINK).substitute(
337 name=name, params=params, firstline=firstline
338 )
339 )
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400340
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700341 if methods:
342 html.append("<h3>Method Details</h3>")
343 for name in methods:
344 dname = name.rsplit("_")[0]
345 html.append(method(name, getattr(resource, name).__doc__))
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400346
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700347 html.append("</body></html>")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400348
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700349 return "\n".join(html)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400350
351
352def document_collection_recursive(resource, path, root_discovery, discovery):
353
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700354 html = document_collection(resource, path, root_discovery, discovery)
Joe Gregorioafc45f22011-02-20 16:11:28 -0500355
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700356 f = open(os.path.join(FLAGS.dest, path + "html"), "w")
Billy SU84d45612020-04-21 06:15:56 +0800357 if sys.version_info.major < 3:
358 html = html.encode("utf-8")
359
360 f.write(html)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700361 f.close()
Joe Gregorioafc45f22011-02-20 16:11:28 -0500362
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700363 for name in dir(resource):
364 if (
365 not name.startswith("_")
366 and callable(getattr(resource, name))
367 and hasattr(getattr(resource, name), "__is_resource__")
368 and discovery != {}
369 ):
370 dname = name.rsplit("_")[0]
371 collection = getattr(resource, name)()
372 document_collection_recursive(
373 collection,
374 path + name + ".",
375 root_discovery,
376 discovery["resources"].get(dname, {}),
377 )
378
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400379
Joe Gregorioafc45f22011-02-20 16:11:28 -0500380def document_api(name, version):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700381 """Document the given API.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400382
383 Args:
384 name: string, Name of the API.
385 version: string, Version of the API.
386 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700387 try:
388 service = build(name, version)
389 except UnknownApiNameOrVersion as e:
390 print("Warning: {} {} found but could not be built.".format(name, version))
391 return
Jon Wayne Parrottfd2f99c2016-02-19 16:02:04 -0800392
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700393 http = build_http()
394 response, content = http.request(
395 uritemplate.expand(
396 FLAGS.discovery_uri_template, {"api": name, "apiVersion": version}
397 )
398 )
399 discovery = json.loads(content)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400400
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700401 version = safe_version(version)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400402
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700403 document_collection_recursive(
404 service, "%s_%s." % (name, version), discovery, discovery
405 )
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400406
Joe Gregorioafc45f22011-02-20 16:11:28 -0500407
Joe Gregoriobb964352013-03-03 20:45:29 -0500408def document_api_from_discovery_document(uri):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700409 """Document the given API.
Joe Gregoriobb964352013-03-03 20:45:29 -0500410
411 Args:
412 uri: string, URI of discovery document.
413 """
Igor Maravić22435292017-01-19 22:28:22 +0100414 http = build_http()
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700415 response, content = http.request(FLAGS.discovery_uri)
416 discovery = json.loads(content)
Bu Sun Kimc9773042019-07-17 14:03:17 -0700417
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700418 service = build_from_document(discovery)
Bu Sun Kimc9773042019-07-17 14:03:17 -0700419
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700420 name = discovery["version"]
421 version = safe_version(discovery["version"])
Bu Sun Kimc9773042019-07-17 14:03:17 -0700422
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700423 document_collection_recursive(
424 service, "%s_%s." % (name, version), discovery, discovery
425 )
426
427
428if __name__ == "__main__":
429 FLAGS = parser.parse_args(sys.argv[1:])
430 if FLAGS.discovery_uri:
431 document_api_from_discovery_document(FLAGS.discovery_uri)
Joe Gregoriobb964352013-03-03 20:45:29 -0500432 else:
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700433 api_directory = collections.defaultdict(list)
434 http = build_http()
435 resp, content = http.request(
436 FLAGS.directory_uri, headers={"X-User-IP": "0.0.0.0"}
437 )
438 if resp.status == 200:
439 directory = json.loads(content)["items"]
440 for api in directory:
441 document_api(api["name"], api["version"])
442 api_directory[api["name"]].append(api["version"])
443
444 # sort by api name and version number
445 for api in api_directory:
446 api_directory[api] = sorted(api_directory[api])
447 api_directory = OrderedDict(
448 sorted(api_directory.items(), key=lambda x: x[0])
449 )
450
451 markdown = []
452 for api, versions in api_directory.items():
453 markdown.append("## %s" % api)
454 for version in versions:
455 markdown.append(
456 "* [%s](http://googleapis.github.io/google-api-python-client/docs/dyn/%s_%s.html)"
457 % (version, api, version)
458 )
459 markdown.append("\n")
460
461 with open("docs/dyn/index.md", "w") as f:
Billy SU84d45612020-04-21 06:15:56 +0800462 markdown = "\n".join(markdown)
463 if sys.version_info.major < 3:
464 markdown = markdown.encode("utf-8")
465 f.write(markdown)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700466
467 else:
468 sys.exit("Failed to load the discovery document.")