Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # -*- coding: utf-8 -*- |
| 3 | # |
| 4 | # Copyright (C) 2012 Google Inc. |
| 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 | |
| 20 | The information for the wiki page is built from data found in all the README |
| 21 | files 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 | """ |
| 31 | |
| 32 | import httplib2 |
| 33 | import itertools |
| 34 | import json |
| 35 | import os |
| 36 | import re |
| 37 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 38 | BASE_HG_URI = ('http://code.google.com/p/google-api-python-client/source/' |
| 39 | 'browse/#hg') |
| 40 | |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 41 | http = httplib2.Http('.cache') |
| 42 | r, c = http.request('https://www.googleapis.com/discovery/v1/apis') |
| 43 | if r.status != 200: |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 44 | raise ValueError('Received non-200 response when retrieving Discovery.') |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 45 | |
| 46 | # Dictionary mapping api names to their discovery description. |
| 47 | DIRECTORY = {} |
| 48 | for 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. |
| 54 | KEYWORDS = { |
| 55 | 'appengine': 'Google App Engine', |
| 56 | 'oauth2': 'OAuth 2.0', |
| 57 | 'cmdline': 'Command-line', |
| 58 | 'django': 'Django', |
| 59 | 'threading': 'Threading', |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 60 | 'pagination': 'Pagination', |
| 61 | 'media': 'Media Upload and Download' |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | |
| 65 | def 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 | |
| 86 | def 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 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 96 | def context_from_sample(api, keywords, dirname, desc, uri): |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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. |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 104 | uri: string, uri of the sample code if provided in the README. |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 105 | |
| 106 | Returns: |
| 107 | A dictionary of values useful for template expansion. |
| 108 | """ |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 109 | if uri is None: |
| 110 | uri = BASE_HG_URI + dirname.replace('/', '%2F') |
| 111 | else: |
| 112 | uri = ''.join(uri) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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, |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 125 | 'uri': uri, |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 126 | 'desc': wiki_escape(desc), |
| 127 | } |
| 128 | return context |
| 129 | |
| 130 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 131 | def keyword_context_from_sample(keywords, dirname, desc, uri): |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 132 | """Return info for expanding a sample into a template. |
| 133 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 134 | Sample may not be about a specific api. |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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. |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 140 | uri: string, uri of the sample code if provided in the README. |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 141 | |
| 142 | Returns: |
| 143 | A dictionary of values useful for template expansion. |
| 144 | """ |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 145 | if uri is None: |
| 146 | uri = BASE_HG_URI + dirname.replace('/', '%2F') |
| 147 | else: |
| 148 | uri = ''.join(uri) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 149 | context = { |
| 150 | 'keywords': keywords, |
| 151 | 'dir': dirname, |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 152 | 'uri': uri, |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 153 | 'desc': wiki_escape(desc), |
| 154 | } |
| 155 | return context |
| 156 | |
| 157 | |
| 158 | def 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) |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 180 | uri = get_lines('uri', lines) |
| 181 | if not uri: |
| 182 | uri = None |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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 | keyword_set.update(keywords) |
| 189 | if not api: |
| 190 | api = [None] |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 191 | samples.append((api[0], keywords, root[1:], desc, uri)) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 192 | |
| 193 | samples.sort() |
| 194 | |
| 195 | return samples, keyword_set |
| 196 | |
| 197 | |
| 198 | def main(): |
| 199 | # Get all the information we need out of the README files in the samples. |
| 200 | samples, keyword_set = scan_readme_files('./samples') |
| 201 | |
| 202 | # Now build a wiki page with all that information. Accumulate all the |
| 203 | # information as string to be concatenated when were done. |
| 204 | page = ['<wiki:toc max_depth="3" />\n= Samples By API =\n'] |
| 205 | |
| 206 | # All the samples, grouped by API. |
| 207 | current_api = None |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 208 | for api, keywords, dirname, desc, uri in samples: |
| 209 | context = context_from_sample(api, keywords, dirname, desc, uri) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 210 | if context is None: |
| 211 | continue |
| 212 | if current_api != api: |
| 213 | page.append(""" |
| 214 | === %(api_icon)s %(api_name)s === |
| 215 | |
| 216 | %(api_desc)s |
| 217 | |
Joe Gregorio | 071f897 | 2012-11-08 13:45:15 -0500 | [diff] [blame^] | 218 | Documentation for the %(api_name)s in [https://google-api-client-libraries.appspot.com/documentation/%(api)s/%(version)s/python/latest/ PyDoc] |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 219 | |
| 220 | """ % context) |
| 221 | current_api = api |
| 222 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 223 | page.append('|| [%(uri)s %(dir)s] || %(desc)s ||\n' % context) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 224 | |
| 225 | # Now group the samples by keywords. |
| 226 | for keyword, keyword_name in KEYWORDS.iteritems(): |
| 227 | if keyword not in keyword_set: |
| 228 | continue |
| 229 | page.append('\n= %s Samples =\n\n' % keyword_name) |
| 230 | page.append('<table border=1 cellspacing=0 cellpadding=8px>\n') |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 231 | for _, keywords, dirname, desc, uri in samples: |
| 232 | context = keyword_context_from_sample(keywords, dirname, desc, uri) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 233 | if keyword not in keywords: |
| 234 | continue |
| 235 | page.append(""" |
| 236 | <tr> |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 237 | <td>[%(uri)s %(dir)s] </td> |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 238 | <td> %(desc)s </td> |
| 239 | </tr>""" % context) |
| 240 | page.append('</table>\n') |
| 241 | |
| 242 | print ''.join(page) |
| 243 | |
| 244 | |
| 245 | if __name__ == '__main__': |
| 246 | main() |