blob: e53724e0494ff5d34e7e4dbebb9c00f8fcb3b592 [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
Anthonios Partheniou32d1c592021-01-14 18:48:59 -050040from googleapiclient.discovery_cache import get_static_doc
Igor Maravić22435292017-01-19 22:28:22 +010041from googleapiclient.http import build_http
Dan O'Mearadd494642020-05-01 07:42:23 -070042from googleapiclient.errors import HttpError
43
Joe Gregorio81d92cc2012-07-09 16:46:02 -040044import uritemplate
45
Joe Gregorio81d92cc2012-07-09 16:46:02 -040046CSS = """<style>
47
48body, h1, h2, h3, div, span, p, pre, a {
49 margin: 0;
50 padding: 0;
51 border: 0;
52 font-weight: inherit;
53 font-style: inherit;
54 font-size: 100%;
55 font-family: inherit;
56 vertical-align: baseline;
57}
58
59body {
60 font-size: 13px;
61 padding: 1em;
62}
63
64h1 {
65 font-size: 26px;
66 margin-bottom: 1em;
67}
68
69h2 {
70 font-size: 24px;
71 margin-bottom: 1em;
72}
73
74h3 {
75 font-size: 20px;
76 margin-bottom: 1em;
77 margin-top: 1em;
78}
79
80pre, code {
81 line-height: 1.5;
82 font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
83}
84
85pre {
86 margin-top: 0.5em;
87}
88
89h1, h2, h3, p {
90 font-family: Arial, sans serif;
91}
92
93h1, h2, h3 {
94 border-bottom: solid #CCC 1px;
95}
96
97.toc_element {
98 margin-top: 0.5em;
99}
100
101.firstline {
102 margin-left: 2 em;
103}
104
105.method {
106 margin-top: 1em;
107 border: solid 1px #CCC;
108 padding: 1em;
109 background: #EEE;
110}
111
112.details {
113 font-weight: bold;
114 font-size: 14px;
115}
116
117</style>
118"""
119
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400120METHOD_TEMPLATE = """<div class="method">
121 <code class="details" id="$name">$name($params)</code>
122 <pre>$doc</pre>
123</div>
124"""
125
126COLLECTION_LINK = """<p class="toc_element">
127 <code><a href="$href">$name()</a></code>
128</p>
129<p class="firstline">Returns the $name Resource.</p>
130"""
131
132METHOD_LINK = """<p class="toc_element">
133 <code><a href="#$name">$name($params)</a></code></p>
134<p class="firstline">$firstline</p>"""
135
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700136BASE = "docs/dyn"
Joe Gregoriobb964352013-03-03 20:45:29 -0500137
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700138DIRECTORY_URI = "https://www.googleapis.com/discovery/v1/apis"
Joe Gregoriobb964352013-03-03 20:45:29 -0500139
Joe Gregorio79daca02013-03-29 16:25:52 -0400140parser = argparse.ArgumentParser(description=__doc__)
Joe Gregoriobb964352013-03-03 20:45:29 -0500141
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700142parser.add_argument(
143 "--discovery_uri_template",
144 default=DISCOVERY_URI,
145 help="URI Template for discovery.",
146)
Joe Gregoriobb964352013-03-03 20:45:29 -0500147
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700148parser.add_argument(
149 "--discovery_uri",
150 default="",
151 help=(
152 "URI of discovery document. If supplied then only "
153 "this API will be documented."
154 ),
155)
Joe Gregoriobb964352013-03-03 20:45:29 -0500156
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700157parser.add_argument(
158 "--directory_uri",
159 default=DIRECTORY_URI,
160 help=("URI of directory document. Unused if --discovery_uri" " is supplied."),
161)
Joe Gregoriobb964352013-03-03 20:45:29 -0500162
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700163parser.add_argument(
164 "--dest", default=BASE, help="Directory name to write documents into."
165)
Joe Gregoriobb964352013-03-03 20:45:29 -0500166
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400167
168def safe_version(version):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700169 """Create a safe version of the verion string.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400170
171 Needed so that we can distinguish between versions
172 and sub-collections in URIs. I.e. we don't want
173 adsense_v1.1 to refer to the '1' collection in the v1
174 version of the adsense api.
175
176 Args:
177 version: string, The version string.
178 Returns:
179 The string with '.' replaced with '_'.
180 """
181
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700182 return version.replace(".", "_")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400183
184
185def unsafe_version(version):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700186 """Undoes what safe_version() does.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400187
188 See safe_version() for the details.
189
190
191 Args:
192 version: string, The safe version string.
193 Returns:
194 The string with '_' replaced with '.'.
195 """
196
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700197 return version.replace("_", ".")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400198
199
200def method_params(doc):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700201 """Document the parameters of a method.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400202
203 Args:
204 doc: string, The method's docstring.
205
206 Returns:
207 The method signature as a string.
208 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700209 doclines = doc.splitlines()
210 if "Args:" in doclines:
211 begin = doclines.index("Args:")
212 if "Returns:" in doclines[begin + 1 :]:
213 end = doclines.index("Returns:", begin)
214 args = doclines[begin + 1 : end]
215 else:
216 args = doclines[begin + 1 :]
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400217
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700218 parameters = []
Anthonios Partheniou4249a7b2020-12-15 20:32:05 -0500219 sorted_parameters = []
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700220 pname = None
221 desc = ""
222
223 def add_param(pname, desc):
224 if pname is None:
225 return
226 if "(required)" not in desc:
227 pname = pname + "=None"
Anthonios Partheniou4249a7b2020-12-15 20:32:05 -0500228 parameters.append(pname)
229 else:
230 # required params should be put straight into sorted_parameters
231 # to maintain order for positional args
232 sorted_parameters.append(pname)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700233
234 for line in args:
Karthikeyan Singaravelan0f60eda2020-08-03 22:12:03 +0530235 m = re.search(r"^\s+([a-zA-Z0-9_]+): (.*)", line)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700236 if m is None:
237 desc += line
238 continue
239 add_param(pname, desc)
240 pname = m.group(1)
241 desc = m.group(2)
242 add_param(pname, desc)
Anthonios Partheniou4249a7b2020-12-15 20:32:05 -0500243 sorted_parameters.extend(sorted(parameters))
244 sorted_parameters = ", ".join(sorted_parameters)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700245 else:
Anthonios Partheniou4249a7b2020-12-15 20:32:05 -0500246 sorted_parameters = ""
247 return sorted_parameters
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400248
249
250def method(name, doc):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700251 """Documents an individual method.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400252
253 Args:
254 name: string, Name of the method.
255 doc: string, The methods docstring.
256 """
257
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700258 params = method_params(doc)
Billy SU45cace82020-04-22 03:46:31 +0800259 if sys.version_info.major >= 3:
260 import html
261 doc = html.escape(doc)
262 else:
263 import cgi
264 doc = cgi.escape(doc)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700265 return string.Template(METHOD_TEMPLATE).substitute(
266 name=name, params=params, doc=doc
267 )
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400268
269
270def breadcrumbs(path, root_discovery):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700271 """Create the breadcrumb trail to this page of documentation.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400272
273 Args:
274 path: string, Dot separated name of the resource.
275 root_discovery: Deserialized discovery document.
276
277 Returns:
278 HTML with links to each of the parent resources of this resource.
279 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700280 parts = path.split(".")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400281
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700282 crumbs = []
283 accumulated = []
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400284
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700285 for i, p in enumerate(parts):
286 prefix = ".".join(accumulated)
287 # The first time through prefix will be [], so we avoid adding in a
288 # superfluous '.' to prefix.
289 if prefix:
290 prefix += "."
291 display = p
292 if i == 0:
293 display = root_discovery.get("title", display)
Karthikeyan Singaravelan0f60eda2020-08-03 22:12:03 +0530294 crumbs.append('<a href="{}.html">{}</a>'.format(prefix + p, display))
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700295 accumulated.append(p)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400296
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700297 return " . ".join(crumbs)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400298
299
300def document_collection(resource, path, root_discovery, discovery, css=CSS):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700301 """Document a single collection in an API.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400302
303 Args:
304 resource: Collection or service being documented.
305 path: string, Dot separated name of the resource.
306 root_discovery: Deserialized discovery document.
307 discovery: Deserialized discovery document, but just the portion that
308 describes the resource.
309 css: string, The CSS to include in the generated file.
310 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700311 collections = []
312 methods = []
313 resource_name = path.split(".")[-2]
314 html = [
315 "<html><body>",
316 css,
317 "<h1>%s</h1>" % breadcrumbs(path[:-1], root_discovery),
318 "<h2>Instance Methods</h2>",
319 ]
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400320
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700321 # Which methods are for collections.
322 for name in dir(resource):
323 if not name.startswith("_") and callable(getattr(resource, name)):
324 if hasattr(getattr(resource, name), "__is_resource__"):
325 collections.append(name)
326 else:
327 methods.append(name)
Joe Gregorioafc45f22011-02-20 16:11:28 -0500328
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700329 # TOC
330 if collections:
331 for name in collections:
332 if not name.startswith("_") and callable(getattr(resource, name)):
333 href = path + name + ".html"
334 html.append(
335 string.Template(COLLECTION_LINK).substitute(href=href, name=name)
336 )
Joe Gregorioafc45f22011-02-20 16:11:28 -0500337
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700338 if methods:
339 for name in methods:
340 if not name.startswith("_") and callable(getattr(resource, name)):
341 doc = getattr(resource, name).__doc__
342 params = method_params(doc)
343 firstline = doc.splitlines()[0]
344 html.append(
345 string.Template(METHOD_LINK).substitute(
346 name=name, params=params, firstline=firstline
347 )
348 )
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400349
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700350 if methods:
351 html.append("<h3>Method Details</h3>")
352 for name in methods:
353 dname = name.rsplit("_")[0]
354 html.append(method(name, getattr(resource, name).__doc__))
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400355
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700356 html.append("</body></html>")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400357
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700358 return "\n".join(html)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400359
360
361def document_collection_recursive(resource, path, root_discovery, discovery):
362
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700363 html = document_collection(resource, path, root_discovery, discovery)
Joe Gregorioafc45f22011-02-20 16:11:28 -0500364
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700365 f = open(os.path.join(FLAGS.dest, path + "html"), "w")
Billy SU84d45612020-04-21 06:15:56 +0800366 if sys.version_info.major < 3:
367 html = html.encode("utf-8")
368
369 f.write(html)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700370 f.close()
Joe Gregorioafc45f22011-02-20 16:11:28 -0500371
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700372 for name in dir(resource):
373 if (
374 not name.startswith("_")
375 and callable(getattr(resource, name))
376 and hasattr(getattr(resource, name), "__is_resource__")
377 and discovery != {}
378 ):
379 dname = name.rsplit("_")[0]
380 collection = getattr(resource, name)()
381 document_collection_recursive(
382 collection,
383 path + name + ".",
384 root_discovery,
385 discovery["resources"].get(dname, {}),
386 )
387
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400388
Bu Sun Kimd059ad82020-07-22 17:02:09 -0700389def document_api(name, version, uri):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700390 """Document the given API.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400391
392 Args:
393 name: string, Name of the API.
394 version: string, Version of the API.
Bu Sun Kimd059ad82020-07-22 17:02:09 -0700395 uri: string, URI of the API's discovery document
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400396 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700397 try:
398 service = build(name, version)
Anthonios Partheniou32d1c592021-01-14 18:48:59 -0500399 content = get_static_doc(name, version)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700400 except UnknownApiNameOrVersion as e:
401 print("Warning: {} {} found but could not be built.".format(name, version))
402 return
Dan O'Mearadd494642020-05-01 07:42:23 -0700403 except HttpError as e:
404 print("Warning: {} {} returned {}.".format(name, version, e))
405 return
Jon Wayne Parrottfd2f99c2016-02-19 16:02:04 -0800406
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700407 discovery = json.loads(content)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400408
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700409 version = safe_version(version)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400410
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700411 document_collection_recursive(
Karthikeyan Singaravelan0f60eda2020-08-03 22:12:03 +0530412 service, "{}_{}.".format(name, version), discovery, discovery
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700413 )
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400414
Joe Gregorioafc45f22011-02-20 16:11:28 -0500415
Joe Gregoriobb964352013-03-03 20:45:29 -0500416def document_api_from_discovery_document(uri):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700417 """Document the given API.
Joe Gregoriobb964352013-03-03 20:45:29 -0500418
419 Args:
420 uri: string, URI of discovery document.
421 """
Igor Maravić22435292017-01-19 22:28:22 +0100422 http = build_http()
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700423 response, content = http.request(FLAGS.discovery_uri)
424 discovery = json.loads(content)
Bu Sun Kimc9773042019-07-17 14:03:17 -0700425
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700426 service = build_from_document(discovery)
Bu Sun Kimc9773042019-07-17 14:03:17 -0700427
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700428 name = discovery["version"]
429 version = safe_version(discovery["version"])
Bu Sun Kimc9773042019-07-17 14:03:17 -0700430
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700431 document_collection_recursive(
Karthikeyan Singaravelan0f60eda2020-08-03 22:12:03 +0530432 service, "{}_{}.".format(name, version), discovery, discovery
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700433 )
434
435
436if __name__ == "__main__":
437 FLAGS = parser.parse_args(sys.argv[1:])
438 if FLAGS.discovery_uri:
439 document_api_from_discovery_document(FLAGS.discovery_uri)
Joe Gregoriobb964352013-03-03 20:45:29 -0500440 else:
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700441 api_directory = collections.defaultdict(list)
442 http = build_http()
443 resp, content = http.request(
444 FLAGS.directory_uri, headers={"X-User-IP": "0.0.0.0"}
445 )
446 if resp.status == 200:
447 directory = json.loads(content)["items"]
448 for api in directory:
Bu Sun Kimd059ad82020-07-22 17:02:09 -0700449 document_api(api["name"], api["version"], api["discoveryRestUrl"])
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700450 api_directory[api["name"]].append(api["version"])
451
452 # sort by api name and version number
453 for api in api_directory:
454 api_directory[api] = sorted(api_directory[api])
455 api_directory = OrderedDict(
456 sorted(api_directory.items(), key=lambda x: x[0])
457 )
458
459 markdown = []
460 for api, versions in api_directory.items():
461 markdown.append("## %s" % api)
462 for version in versions:
463 markdown.append(
464 "* [%s](http://googleapis.github.io/google-api-python-client/docs/dyn/%s_%s.html)"
Bu Sun Kim673ec5c2020-11-16 11:05:03 -0700465 % (version, api, safe_version(version))
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700466 )
467 markdown.append("\n")
468
469 with open("docs/dyn/index.md", "w") as f:
Billy SU84d45612020-04-21 06:15:56 +0800470 markdown = "\n".join(markdown)
471 if sys.version_info.major < 3:
472 markdown = markdown.encode("utf-8")
473 f.write(markdown)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700474
475 else:
476 sys.exit("Failed to load the discovery document.")