• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/'
40               'browse/#hg')
41
42http = httplib2.Http('.cache')
43r, c =  http.request('https://www.googleapis.com/discovery/v1/apis')
44if r.status != 200:
45  raise ValueError('Received non-200 response when retrieving Discovery.')
46
47# Dictionary mapping api names to their discovery description.
48DIRECTORY = {}
49for item in json.loads(c)['items']:
50  if item['preferred']:
51    DIRECTORY[item['name']] = item
52
53# A list of valid keywords. Should not be taken as complete, add to
54# this list as needed.
55KEYWORDS = {
56    'appengine': 'Google App Engine',
57    'oauth2': 'OAuth 2.0',
58    'cmdline': 'Command-line',
59    'django': 'Django',
60    'threading': 'Threading',
61    'pagination': 'Pagination',
62    'media': 'Media Upload and Download'
63    }
64
65
66def get_lines(name, lines):
67  """Return lines that begin with name.
68
69  Lines are expected to look like:
70
71     name: space separated values
72
73  Args:
74    name: string, parameter name.
75    lines: iterable of string, lines in the file.
76
77  Returns:
78    List of values in the lines that match.
79  """
80  retval = []
81  matches = itertools.ifilter(lambda x: x.startswith(name + ':'), lines)
82  for line in matches:
83    retval.extend(line[len(name)+1:].split())
84  return retval
85
86
87def wiki_escape(s):
88  """Detect WikiSyntax (i.e. InterCaps, a.k.a. CamelCase) and escape it."""
89  ret = []
90  for word in s.split():
91    if re.match(r'[A-Z]+[a-z]+[A-Z]', word):
92      word = '!%s' % word
93    ret.append(word)
94  return ' '.join(ret)
95
96
97def context_from_sample(api, keywords, dirname, desc, uri):
98  """Return info for expanding a sample into a template.
99
100  Args:
101    api: string, name of api.
102    keywords: list of string, list of keywords for the given api.
103    dirname: string, directory name of the sample.
104    desc: string, long description of the sample.
105    uri: string, uri of the sample code if provided in the README.
106
107  Returns:
108    A dictionary of values useful for template expansion.
109  """
110  if uri is None:
111    uri = BASE_HG_URI + dirname.replace('/', '%2F')
112  else:
113    uri = ''.join(uri)
114  if api is None:
115    return None
116  else:
117    entry = DIRECTORY[api]
118    context = {
119        'api': api,
120        'version': entry['version'],
121        'api_name': wiki_escape(entry.get('title', entry.get('description'))),
122        'api_desc': wiki_escape(entry['description']),
123        'api_icon': entry['icons']['x32'],
124        'keywords': keywords,
125        'dir': dirname,
126        'uri': uri,
127        'desc': wiki_escape(desc),
128        }
129    return context
130
131
132def keyword_context_from_sample(keywords, dirname, desc, uri):
133  """Return info for expanding a sample into a template.
134
135  Sample may not be about a specific api.
136
137  Args:
138    keywords: list of string, list of keywords for the given api.
139    dirname: string, directory name of the sample.
140    desc: string, long description of the sample.
141    uri: string, uri of the sample code if provided in the README.
142
143  Returns:
144    A dictionary of values useful for template expansion.
145  """
146  if uri is None:
147    uri = BASE_HG_URI + dirname.replace('/', '%2F')
148  else:
149    uri = ''.join(uri)
150  context = {
151      'keywords': keywords,
152      'dir': dirname,
153      'uri': uri,
154      'desc': wiki_escape(desc),
155      }
156  return context
157
158
159def scan_readme_files(dirname):
160  """Scans all subdirs of dirname for README files.
161
162  Args:
163    dirname: string, name of directory to walk.
164
165  Returns:
166    (samples, keyword_set): list of information about all samples, the union
167      of all keywords found.
168  """
169  samples = []
170  keyword_set = set()
171
172  for root, dirs, files in os.walk(dirname):
173    if 'README' in files:
174      filename = os.path.join(root, 'README')
175      with open(filename, 'r') as f:
176        content = f.read()
177        lines = content.splitlines()
178        desc = ' '.join(itertools.takewhile(lambda x: x, lines))
179        api = get_lines('api', lines)
180        keywords = get_lines('keywords', lines)
181        uri = get_lines('uri', lines)
182        if not uri:
183          uri = None
184
185        for k in keywords:
186          if k not in KEYWORDS:
187            raise ValueError(
188                '%s is not a valid keyword in file %s' % (k, filename))
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=== %(api_icon)s %(api_name)s ===
216
217%(api_desc)s
218
219Documentation for the %(api_name)s in [https://google-api-client-libraries.appspot.com/documentation/%(api)s/%(version)s/python/latest/ PyDoc]
220
221""" % context)
222      current_api = api
223
224    page.append('|| [%(uri)s %(dir)s] || %(desc)s ||\n' % context)
225
226  # Now group the samples by keywords.
227  for keyword, keyword_name in KEYWORDS.iteritems():
228    if keyword not in keyword_set:
229      continue
230    page.append('\n= %s Samples =\n\n' % keyword_name)
231    page.append('<table border=1 cellspacing=0 cellpadding=8px>\n')
232    for _, keywords, dirname, desc, uri in samples:
233      context = keyword_context_from_sample(keywords, dirname, desc, uri)
234      if keyword not in keywords:
235        continue
236      page.append("""
237<tr>
238  <td>[%(uri)s %(dir)s] </td>
239  <td> %(desc)s </td>
240</tr>""" % context)
241    page.append('</table>\n')
242
243  print(''.join(page))
244
245
246if __name__ == '__main__':
247  main()
248