1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Copyright 2014 Google Inc. All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18"""Build wiki page with a list of all samples.
19
20The information for the wiki page is built from data found in all the README
21files in the samples. The format of the README file is:
22
23
24   Description is everything up to the first blank line.
25
26   api: plus  (Used to look up the long name in discovery).
27   keywords: appengine (such as appengine, oauth2, cmdline)
28
29   The rest of the file is ignored when it comes to building the index.
30"""
31from __future__ import print_function
32
33import httplib2
34import itertools
35import json
36import os
37import re
38
39BASE_HG_URI = "http://code.google.com/p/google-api-python-client/source/" "browse/#hg"
40
41http = httplib2.Http(".cache")
42r, c = http.request("https://www.googleapis.com/discovery/v1/apis")
43if r.status != 200:
44    raise ValueError("Received non-200 response when retrieving Discovery.")
45
46# Dictionary mapping api names to their discovery description.
47DIRECTORY = {}
48for item in json.loads(c)["items"]:
49    if item["preferred"]:
50        DIRECTORY[item["name"]] = item
51
52# A list of valid keywords. Should not be taken as complete, add to
53# this list as needed.
54KEYWORDS = {
55    "appengine": "Google App Engine",
56    "oauth2": "OAuth 2.0",
57    "cmdline": "Command-line",
58    "django": "Django",
59    "threading": "Threading",
60    "pagination": "Pagination",
61    "media": "Media Upload and Download",
62}
63
64
65def get_lines(name, lines):
66    """Return lines that begin with name.
67
68  Lines are expected to look like:
69
70     name: space separated values
71
72  Args:
73    name: string, parameter name.
74    lines: iterable of string, lines in the file.
75
76  Returns:
77    List of values in the lines that match.
78  """
79    retval = []
80    matches = itertools.ifilter(lambda x: x.startswith(name + ":"), lines)
81    for line in matches:
82        retval.extend(line[len(name) + 1 :].split())
83    return retval
84
85
86def wiki_escape(s):
87    """Detect WikiSyntax (i.e. InterCaps, a.k.a. CamelCase) and escape it."""
88    ret = []
89    for word in s.split():
90        if re.match(r"[A-Z]+[a-z]+[A-Z]", word):
91            word = "!%s" % word
92        ret.append(word)
93    return " ".join(ret)
94
95
96def context_from_sample(api, keywords, dirname, desc, uri):
97    """Return info for expanding a sample into a template.
98
99  Args:
100    api: string, name of api.
101    keywords: list of string, list of keywords for the given api.
102    dirname: string, directory name of the sample.
103    desc: string, long description of the sample.
104    uri: string, uri of the sample code if provided in the README.
105
106  Returns:
107    A dictionary of values useful for template expansion.
108  """
109    if uri is None:
110        uri = BASE_HG_URI + dirname.replace("/", "%2F")
111    else:
112        uri = "".join(uri)
113    if api is None:
114        return None
115    else:
116        entry = DIRECTORY[api]
117        context = {
118            "api": api,
119            "version": entry["version"],
120            "api_name": wiki_escape(entry.get("title", entry.get("description"))),
121            "api_desc": wiki_escape(entry["description"]),
122            "api_icon": entry["icons"]["x32"],
123            "keywords": keywords,
124            "dir": dirname,
125            "uri": uri,
126            "desc": wiki_escape(desc),
127        }
128        return context
129
130
131def keyword_context_from_sample(keywords, dirname, desc, uri):
132    """Return info for expanding a sample into a template.
133
134  Sample may not be about a specific api.
135
136  Args:
137    keywords: list of string, list of keywords for the given api.
138    dirname: string, directory name of the sample.
139    desc: string, long description of the sample.
140    uri: string, uri of the sample code if provided in the README.
141
142  Returns:
143    A dictionary of values useful for template expansion.
144  """
145    if uri is None:
146        uri = BASE_HG_URI + dirname.replace("/", "%2F")
147    else:
148        uri = "".join(uri)
149    context = {
150        "keywords": keywords,
151        "dir": dirname,
152        "uri": uri,
153        "desc": wiki_escape(desc),
154    }
155    return context
156
157
158def scan_readme_files(dirname):
159    """Scans all subdirs of dirname for README files.
160
161  Args:
162    dirname: string, name of directory to walk.
163
164  Returns:
165    (samples, keyword_set): list of information about all samples, the union
166      of all keywords found.
167  """
168    samples = []
169    keyword_set = set()
170
171    for root, dirs, files in os.walk(dirname):
172        if "README" in files:
173            filename = os.path.join(root, "README")
174            with open(filename, "r") as f:
175                content = f.read()
176                lines = content.splitlines()
177                desc = " ".join(itertools.takewhile(lambda x: x, lines))
178                api = get_lines("api", lines)
179                keywords = get_lines("keywords", lines)
180                uri = get_lines("uri", lines)
181                if not uri:
182                    uri = None
183
184                for k in keywords:
185                    if k not in KEYWORDS:
186                        raise ValueError(
187                            "%s is not a valid keyword in file %s" % (k, filename)
188                        )
189                keyword_set.update(keywords)
190                if not api:
191                    api = [None]
192                samples.append((api[0], keywords, root[1:], desc, uri))
193
194    samples.sort()
195
196    return samples, keyword_set
197
198
199def main():
200    # Get all the information we need out of the README files in the samples.
201    samples, keyword_set = scan_readme_files("./samples")
202
203    # Now build a wiki page with all that information. Accumulate all the
204    # information as string to be concatenated when were done.
205    page = ['<wiki:toc max_depth="3" />\n= Samples By API =\n']
206
207    # All the samples, grouped by API.
208    current_api = None
209    for api, keywords, dirname, desc, uri in samples:
210        context = context_from_sample(api, keywords, dirname, desc, uri)
211        if context is None:
212            continue
213        if current_api != api:
214            page.append(
215                """
216=== %(api_icon)s %(api_name)s ===
217
218%(api_desc)s
219
220Documentation for the %(api_name)s in [https://google-api-client-libraries.appspot.com/documentation/%(api)s/%(version)s/python/latest/ PyDoc]
221
222"""
223                % context
224            )
225            current_api = api
226
227        page.append("|| [%(uri)s %(dir)s] || %(desc)s ||\n" % context)
228
229    # Now group the samples by keywords.
230    for keyword, keyword_name in KEYWORDS.iteritems():
231        if keyword not in keyword_set:
232            continue
233        page.append("\n= %s Samples =\n\n" % keyword_name)
234        page.append("<table border=1 cellspacing=0 cellpadding=8px>\n")
235        for _, keywords, dirname, desc, uri in samples:
236            context = keyword_context_from_sample(keywords, dirname, desc, uri)
237            if keyword not in keywords:
238                continue
239            page.append(
240                """
241<tr>
242  <td>[%(uri)s %(dir)s] </td>
243  <td> %(desc)s </td>
244</tr>"""
245                % context
246            )
247        page.append("</table>\n")
248
249    print("".join(page))
250
251
252if __name__ == "__main__":
253    main()
254