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