blob: 2537e7c06eeadedd1daae7e734081640b17e2ef5 [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
Dan O'Mearadd494642020-05-01 07:42:23 -070041from googleapiclient.errors import HttpError
42
Joe Gregorio81d92cc2012-07-09 16:46:02 -040043import uritemplate
44
Joe Gregorio81d92cc2012-07-09 16:46:02 -040045CSS = """<style>
46
47body, h1, h2, h3, div, span, p, pre, a {
48 margin: 0;
49 padding: 0;
50 border: 0;
51 font-weight: inherit;
52 font-style: inherit;
53 font-size: 100%;
54 font-family: inherit;
55 vertical-align: baseline;
56}
57
58body {
59 font-size: 13px;
60 padding: 1em;
61}
62
63h1 {
64 font-size: 26px;
65 margin-bottom: 1em;
66}
67
68h2 {
69 font-size: 24px;
70 margin-bottom: 1em;
71}
72
73h3 {
74 font-size: 20px;
75 margin-bottom: 1em;
76 margin-top: 1em;
77}
78
79pre, code {
80 line-height: 1.5;
81 font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
82}
83
84pre {
85 margin-top: 0.5em;
86}
87
88h1, h2, h3, p {
89 font-family: Arial, sans serif;
90}
91
92h1, h2, h3 {
93 border-bottom: solid #CCC 1px;
94}
95
96.toc_element {
97 margin-top: 0.5em;
98}
99
100.firstline {
101 margin-left: 2 em;
102}
103
104.method {
105 margin-top: 1em;
106 border: solid 1px #CCC;
107 padding: 1em;
108 background: #EEE;
109}
110
111.details {
112 font-weight: bold;
113 font-size: 14px;
114}
115
116</style>
117"""
118
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400119METHOD_TEMPLATE = """<div class="method">
120 <code class="details" id="$name">$name($params)</code>
121 <pre>$doc</pre>
122</div>
123"""
124
125COLLECTION_LINK = """<p class="toc_element">
126 <code><a href="$href">$name()</a></code>
127</p>
128<p class="firstline">Returns the $name Resource.</p>
129"""
130
131METHOD_LINK = """<p class="toc_element">
132 <code><a href="#$name">$name($params)</a></code></p>
133<p class="firstline">$firstline</p>"""
134
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700135BASE = "docs/dyn"
Joe Gregoriobb964352013-03-03 20:45:29 -0500136
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700137DIRECTORY_URI = "https://www.googleapis.com/discovery/v1/apis"
Joe Gregoriobb964352013-03-03 20:45:29 -0500138
Joe Gregorio79daca02013-03-29 16:25:52 -0400139parser = argparse.ArgumentParser(description=__doc__)
Joe Gregoriobb964352013-03-03 20:45:29 -0500140
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700141parser.add_argument(
142 "--discovery_uri_template",
143 default=DISCOVERY_URI,
144 help="URI Template for discovery.",
145)
Joe Gregoriobb964352013-03-03 20:45:29 -0500146
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700147parser.add_argument(
148 "--discovery_uri",
149 default="",
150 help=(
151 "URI of discovery document. If supplied then only "
152 "this API will be documented."
153 ),
154)
Joe Gregoriobb964352013-03-03 20:45:29 -0500155
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700156parser.add_argument(
157 "--directory_uri",
158 default=DIRECTORY_URI,
159 help=("URI of directory document. Unused if --discovery_uri" " is supplied."),
160)
Joe Gregoriobb964352013-03-03 20:45:29 -0500161
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700162parser.add_argument(
163 "--dest", default=BASE, help="Directory name to write documents into."
164)
Joe Gregoriobb964352013-03-03 20:45:29 -0500165
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400166
167def safe_version(version):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700168 """Create a safe version of the verion string.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400169
170 Needed so that we can distinguish between versions
171 and sub-collections in URIs. I.e. we don't want
172 adsense_v1.1 to refer to the '1' collection in the v1
173 version of the adsense api.
174
175 Args:
176 version: string, The version string.
177 Returns:
178 The string with '.' replaced with '_'.
179 """
180
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700181 return version.replace(".", "_")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400182
183
184def unsafe_version(version):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700185 """Undoes what safe_version() does.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400186
187 See safe_version() for the details.
188
189
190 Args:
191 version: string, The safe version string.
192 Returns:
193 The string with '_' replaced with '.'.
194 """
195
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700196 return version.replace("_", ".")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400197
198
199def method_params(doc):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700200 """Document the parameters of a method.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400201
202 Args:
203 doc: string, The method's docstring.
204
205 Returns:
206 The method signature as a string.
207 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700208 doclines = doc.splitlines()
209 if "Args:" in doclines:
210 begin = doclines.index("Args:")
211 if "Returns:" in doclines[begin + 1 :]:
212 end = doclines.index("Returns:", begin)
213 args = doclines[begin + 1 : end]
214 else:
215 args = doclines[begin + 1 :]
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400216
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700217 parameters = []
218 pname = None
219 desc = ""
220
221 def add_param(pname, desc):
222 if pname is None:
223 return
224 if "(required)" not in desc:
225 pname = pname + "=None"
226 parameters.append(pname)
227
228 for line in args:
Karthikeyan Singaravelan0f60eda2020-08-03 22:12:03 +0530229 m = re.search(r"^\s+([a-zA-Z0-9_]+): (.*)", line)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700230 if m is None:
231 desc += line
232 continue
233 add_param(pname, desc)
234 pname = m.group(1)
235 desc = m.group(2)
236 add_param(pname, desc)
237 parameters = ", ".join(parameters)
238 else:
239 parameters = ""
240 return parameters
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400241
242
243def method(name, doc):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700244 """Documents an individual method.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400245
246 Args:
247 name: string, Name of the method.
248 doc: string, The methods docstring.
249 """
250
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700251 params = method_params(doc)
Billy SU45cace82020-04-22 03:46:31 +0800252 if sys.version_info.major >= 3:
253 import html
254 doc = html.escape(doc)
255 else:
256 import cgi
257 doc = cgi.escape(doc)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700258 return string.Template(METHOD_TEMPLATE).substitute(
259 name=name, params=params, doc=doc
260 )
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400261
262
263def breadcrumbs(path, root_discovery):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700264 """Create the breadcrumb trail to this page of documentation.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400265
266 Args:
267 path: string, Dot separated name of the resource.
268 root_discovery: Deserialized discovery document.
269
270 Returns:
271 HTML with links to each of the parent resources of this resource.
272 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700273 parts = path.split(".")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400274
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700275 crumbs = []
276 accumulated = []
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400277
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700278 for i, p in enumerate(parts):
279 prefix = ".".join(accumulated)
280 # The first time through prefix will be [], so we avoid adding in a
281 # superfluous '.' to prefix.
282 if prefix:
283 prefix += "."
284 display = p
285 if i == 0:
286 display = root_discovery.get("title", display)
Karthikeyan Singaravelan0f60eda2020-08-03 22:12:03 +0530287 crumbs.append('<a href="{}.html">{}</a>'.format(prefix + p, display))
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700288 accumulated.append(p)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400289
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700290 return " . ".join(crumbs)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400291
292
293def document_collection(resource, path, root_discovery, discovery, css=CSS):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700294 """Document a single collection in an API.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400295
296 Args:
297 resource: Collection or service being documented.
298 path: string, Dot separated name of the resource.
299 root_discovery: Deserialized discovery document.
300 discovery: Deserialized discovery document, but just the portion that
301 describes the resource.
302 css: string, The CSS to include in the generated file.
303 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700304 collections = []
305 methods = []
306 resource_name = path.split(".")[-2]
307 html = [
308 "<html><body>",
309 css,
310 "<h1>%s</h1>" % breadcrumbs(path[:-1], root_discovery),
311 "<h2>Instance Methods</h2>",
312 ]
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400313
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700314 # Which methods are for collections.
315 for name in dir(resource):
316 if not name.startswith("_") and callable(getattr(resource, name)):
317 if hasattr(getattr(resource, name), "__is_resource__"):
318 collections.append(name)
319 else:
320 methods.append(name)
Joe Gregorioafc45f22011-02-20 16:11:28 -0500321
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700322 # TOC
323 if collections:
324 for name in collections:
325 if not name.startswith("_") and callable(getattr(resource, name)):
326 href = path + name + ".html"
327 html.append(
328 string.Template(COLLECTION_LINK).substitute(href=href, name=name)
329 )
Joe Gregorioafc45f22011-02-20 16:11:28 -0500330
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700331 if methods:
332 for name in methods:
333 if not name.startswith("_") and callable(getattr(resource, name)):
334 doc = getattr(resource, name).__doc__
335 params = method_params(doc)
336 firstline = doc.splitlines()[0]
337 html.append(
338 string.Template(METHOD_LINK).substitute(
339 name=name, params=params, firstline=firstline
340 )
341 )
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400342
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700343 if methods:
344 html.append("<h3>Method Details</h3>")
345 for name in methods:
346 dname = name.rsplit("_")[0]
347 html.append(method(name, getattr(resource, name).__doc__))
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400348
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700349 html.append("</body></html>")
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400350
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700351 return "\n".join(html)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400352
353
354def document_collection_recursive(resource, path, root_discovery, discovery):
355
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700356 html = document_collection(resource, path, root_discovery, discovery)
Joe Gregorioafc45f22011-02-20 16:11:28 -0500357
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700358 f = open(os.path.join(FLAGS.dest, path + "html"), "w")
Billy SU84d45612020-04-21 06:15:56 +0800359 if sys.version_info.major < 3:
360 html = html.encode("utf-8")
361
362 f.write(html)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700363 f.close()
Joe Gregorioafc45f22011-02-20 16:11:28 -0500364
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700365 for name in dir(resource):
366 if (
367 not name.startswith("_")
368 and callable(getattr(resource, name))
369 and hasattr(getattr(resource, name), "__is_resource__")
370 and discovery != {}
371 ):
372 dname = name.rsplit("_")[0]
373 collection = getattr(resource, name)()
374 document_collection_recursive(
375 collection,
376 path + name + ".",
377 root_discovery,
378 discovery["resources"].get(dname, {}),
379 )
380
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400381
Bu Sun Kimd059ad82020-07-22 17:02:09 -0700382def document_api(name, version, uri):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700383 """Document the given API.
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400384
385 Args:
386 name: string, Name of the API.
387 version: string, Version of the API.
Bu Sun Kimd059ad82020-07-22 17:02:09 -0700388 uri: string, URI of the API's discovery document
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400389 """
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700390 try:
391 service = build(name, version)
392 except UnknownApiNameOrVersion as e:
393 print("Warning: {} {} found but could not be built.".format(name, version))
394 return
Dan O'Mearadd494642020-05-01 07:42:23 -0700395 except HttpError as e:
396 print("Warning: {} {} returned {}.".format(name, version, e))
397 return
Jon Wayne Parrottfd2f99c2016-02-19 16:02:04 -0800398
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700399 http = build_http()
400 response, content = http.request(
Bu Sun Kimd059ad82020-07-22 17:02:09 -0700401 uri or uritemplate.expand(
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700402 FLAGS.discovery_uri_template, {"api": name, "apiVersion": version}
403 )
404 )
405 discovery = json.loads(content)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400406
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700407 version = safe_version(version)
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400408
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700409 document_collection_recursive(
Karthikeyan Singaravelan0f60eda2020-08-03 22:12:03 +0530410 service, "{}_{}.".format(name, version), discovery, discovery
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700411 )
Joe Gregorio81d92cc2012-07-09 16:46:02 -0400412
Joe Gregorioafc45f22011-02-20 16:11:28 -0500413
Joe Gregoriobb964352013-03-03 20:45:29 -0500414def document_api_from_discovery_document(uri):
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700415 """Document the given API.
Joe Gregoriobb964352013-03-03 20:45:29 -0500416
417 Args:
418 uri: string, URI of discovery document.
419 """
Igor Maravić22435292017-01-19 22:28:22 +0100420 http = build_http()
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700421 response, content = http.request(FLAGS.discovery_uri)
422 discovery = json.loads(content)
Bu Sun Kimc9773042019-07-17 14:03:17 -0700423
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700424 service = build_from_document(discovery)
Bu Sun Kimc9773042019-07-17 14:03:17 -0700425
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700426 name = discovery["version"]
427 version = safe_version(discovery["version"])
Bu Sun Kimc9773042019-07-17 14:03:17 -0700428
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700429 document_collection_recursive(
Karthikeyan Singaravelan0f60eda2020-08-03 22:12:03 +0530430 service, "{}_{}.".format(name, version), discovery, discovery
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700431 )
432
433
434if __name__ == "__main__":
435 FLAGS = parser.parse_args(sys.argv[1:])
436 if FLAGS.discovery_uri:
437 document_api_from_discovery_document(FLAGS.discovery_uri)
Joe Gregoriobb964352013-03-03 20:45:29 -0500438 else:
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700439 api_directory = collections.defaultdict(list)
440 http = build_http()
441 resp, content = http.request(
442 FLAGS.directory_uri, headers={"X-User-IP": "0.0.0.0"}
443 )
444 if resp.status == 200:
445 directory = json.loads(content)["items"]
446 for api in directory:
Bu Sun Kimd059ad82020-07-22 17:02:09 -0700447 document_api(api["name"], api["version"], api["discoveryRestUrl"])
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700448 api_directory[api["name"]].append(api["version"])
449
450 # sort by api name and version number
451 for api in api_directory:
452 api_directory[api] = sorted(api_directory[api])
453 api_directory = OrderedDict(
454 sorted(api_directory.items(), key=lambda x: x[0])
455 )
456
457 markdown = []
458 for api, versions in api_directory.items():
459 markdown.append("## %s" % api)
460 for version in versions:
461 markdown.append(
462 "* [%s](http://googleapis.github.io/google-api-python-client/docs/dyn/%s_%s.html)"
Bu Sun Kim673ec5c2020-11-16 11:05:03 -0700463 % (version, api, safe_version(version))
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700464 )
465 markdown.append("\n")
466
467 with open("docs/dyn/index.md", "w") as f:
Billy SU84d45612020-04-21 06:15:56 +0800468 markdown = "\n".join(markdown)
469 if sys.version_info.major < 3:
470 markdown = markdown.encode("utf-8")
471 f.write(markdown)
Bu Sun Kim66bb32c2019-10-30 10:11:58 -0700472
473 else:
474 sys.exit("Failed to load the discovery document.")