Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | # -*- coding: utf-8 -*- |
| 3 | # |
Craig Citro | 751b7fb | 2014-09-23 11:20:38 -0700 | [diff] [blame] | 4 | # Copyright 2014 Google Inc. All Rights Reserved. |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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 | """ |
INADA Naoki | e8d8782 | 2014-08-20 15:25:24 +0900 | [diff] [blame] | 31 | from __future__ import print_function |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 32 | |
| 33 | import httplib2 |
| 34 | import itertools |
| 35 | import json |
| 36 | import os |
| 37 | import re |
| 38 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 39 | BASE_HG_URI = ('http://code.google.com/p/google-api-python-client/source/' |
| 40 | 'browse/#hg') |
| 41 | |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 42 | http = httplib2.Http('.cache') |
| 43 | r, c = http.request('https://www.googleapis.com/discovery/v1/apis') |
| 44 | if r.status != 200: |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 45 | raise ValueError('Received non-200 response when retrieving Discovery.') |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 46 | |
| 47 | # Dictionary mapping api names to their discovery description. |
| 48 | DIRECTORY = {} |
| 49 | for 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. |
| 55 | KEYWORDS = { |
| 56 | 'appengine': 'Google App Engine', |
| 57 | 'oauth2': 'OAuth 2.0', |
| 58 | 'cmdline': 'Command-line', |
| 59 | 'django': 'Django', |
| 60 | 'threading': 'Threading', |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 61 | 'pagination': 'Pagination', |
| 62 | 'media': 'Media Upload and Download' |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 63 | } |
| 64 | |
| 65 | |
| 66 | def 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 | |
| 87 | def 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 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 97 | def context_from_sample(api, keywords, dirname, desc, uri): |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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. |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 105 | uri: string, uri of the sample code if provided in the README. |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 106 | |
| 107 | Returns: |
| 108 | A dictionary of values useful for template expansion. |
| 109 | """ |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 110 | if uri is None: |
| 111 | uri = BASE_HG_URI + dirname.replace('/', '%2F') |
| 112 | else: |
| 113 | uri = ''.join(uri) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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, |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 126 | 'uri': uri, |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 127 | 'desc': wiki_escape(desc), |
| 128 | } |
| 129 | return context |
| 130 | |
| 131 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 132 | def keyword_context_from_sample(keywords, dirname, desc, uri): |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 133 | """Return info for expanding a sample into a template. |
| 134 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 135 | Sample may not be about a specific api. |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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. |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 141 | uri: string, uri of the sample code if provided in the README. |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 142 | |
| 143 | Returns: |
| 144 | A dictionary of values useful for template expansion. |
| 145 | """ |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 146 | if uri is None: |
| 147 | uri = BASE_HG_URI + dirname.replace('/', '%2F') |
| 148 | else: |
| 149 | uri = ''.join(uri) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 150 | context = { |
| 151 | 'keywords': keywords, |
| 152 | 'dir': dirname, |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 153 | 'uri': uri, |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 154 | 'desc': wiki_escape(desc), |
| 155 | } |
| 156 | return context |
| 157 | |
| 158 | |
| 159 | def 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) |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 181 | uri = get_lines('uri', lines) |
| 182 | if not uri: |
| 183 | uri = None |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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] |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 192 | samples.append((api[0], keywords, root[1:], desc, uri)) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 193 | |
| 194 | samples.sort() |
| 195 | |
| 196 | return samples, keyword_set |
| 197 | |
| 198 | |
| 199 | def 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 |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 209 | for api, keywords, dirname, desc, uri in samples: |
| 210 | context = context_from_sample(api, keywords, dirname, desc, uri) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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 | |
Joe Gregorio | 071f897 | 2012-11-08 13:45:15 -0500 | [diff] [blame] | 219 | 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] | 220 | |
| 221 | """ % context) |
| 222 | current_api = api |
| 223 | |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 224 | page.append('|| [%(uri)s %(dir)s] || %(desc)s ||\n' % context) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 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') |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 232 | for _, keywords, dirname, desc, uri in samples: |
| 233 | context = keyword_context_from_sample(keywords, dirname, desc, uri) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 234 | if keyword not in keywords: |
| 235 | continue |
| 236 | page.append(""" |
| 237 | <tr> |
Joe Gregorio | a633c79 | 2012-08-24 11:27:16 -0400 | [diff] [blame] | 238 | <td>[%(uri)s %(dir)s] </td> |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 239 | <td> %(desc)s </td> |
| 240 | </tr>""" % context) |
| 241 | page.append('</table>\n') |
| 242 | |
INADA Naoki | e8d8782 | 2014-08-20 15:25:24 +0900 | [diff] [blame] | 243 | print(''.join(page)) |
Joe Gregorio | 9d56b5a | 2012-03-30 09:21:26 -0400 | [diff] [blame] | 244 | |
| 245 | |
| 246 | if __name__ == '__main__': |
| 247 | main() |