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