Automatically generate Samples wiki page from README files.

Reviewed in http://codereview.appspot.com/5900069/.

Index: samples-index.py
===================================================================
new file mode 100644
diff --git a/Makefile b/Makefile
index 8d6962b..3786b7f 100644
--- a/Makefile
+++ b/Makefile
@@ -19,6 +19,11 @@
 docs:
 	cd docs; ./build.sh
 	python describe.py
+	python samples-index.py ../google-api-python-client.wiki/SampleApps.wiki
+
+.PHONY: wiki
+wiki:
+	python samples-index.py > ../google-api-python-client.wiki/SampleApps.wiki
 
 .PHONY: prerelease
 prerelease:
diff --git a/samples-index.py b/samples-index.py
new file mode 100644
index 0000000..35e1c16
--- /dev/null
+++ b/samples-index.py
@@ -0,0 +1,229 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Build wiki page with a list of all samples.
+
+The information for the wiki page is built from data found in all the README
+files in the samples. The format of the README file is:
+
+
+   Description is everything up to the first blank line.
+
+   api: plus  (Used to look up the long name in discovery).
+   keywords: appengine (such as appengine, oauth2, cmdline)
+
+   The rest of the file is ignored when it comes to building the index.
+"""
+
+import httplib2
+import itertools
+import json
+import os
+import re
+
+http = httplib2.Http('.cache')
+r, c =  http.request('https://www.googleapis.com/discovery/v1/apis')
+if r.status != 200:
+  raise ValueError('Received non-200 response when retrieving Discovery document.')
+
+# Dictionary mapping api names to their discovery description.
+DIRECTORY = {}
+for item in json.loads(c)['items']:
+  if item['preferred']:
+    DIRECTORY[item['name']] = item
+
+# A list of valid keywords. Should not be taken as complete, add to
+# this list as needed.
+KEYWORDS = {
+    'appengine': 'Google App Engine',
+    'oauth2': 'OAuth 2.0',
+    'cmdline': 'Command-line',
+    'django': 'Django',
+    'threading': 'Threading',
+    'pagination': 'Pagination'
+    }
+
+
+def get_lines(name, lines):
+  """Return lines that begin with name.
+
+  Lines are expected to look like:
+
+     name: space separated values
+
+  Args:
+    name: string, parameter name.
+    lines: iterable of string, lines in the file.
+
+  Returns:
+    List of values in the lines that match.
+  """
+  retval = []
+  matches = itertools.ifilter(lambda x: x.startswith(name + ':'), lines)
+  for line in matches:
+    retval.extend(line[len(name)+1:].split())
+  return retval
+
+
+def wiki_escape(s):
+  """Detect WikiSyntax (i.e. InterCaps, a.k.a. CamelCase) and escape it."""
+  ret = []
+  for word in s.split():
+    if re.match(r'[A-Z]+[a-z]+[A-Z]', word):
+      word = '!%s' % word
+    ret.append(word)
+  return ' '.join(ret)
+
+
+def context_from_sample(api, keywords, dirname, desc):
+  """Return info for expanding a sample into a template.
+
+  Args:
+    api: string, name of api.
+    keywords: list of string, list of keywords for the given api.
+    dirname: string, directory name of the sample.
+    desc: string, long description of the sample.
+
+  Returns:
+    A dictionary of values useful for template expansion.
+  """
+  if api is None:
+    return None
+  else:
+    entry = DIRECTORY[api]
+    context = {
+        'api': api,
+        'version': entry['version'],
+        'api_name': wiki_escape(entry.get('title', entry.get('description'))),
+        'api_desc': wiki_escape(entry['description']),
+        'api_icon': entry['icons']['x32'],
+        'keywords': keywords,
+        'dir': dirname,
+        'dir_escaped': dirname.replace('/', '%2F'),
+        'desc': wiki_escape(desc),
+        }
+    return context
+
+
+def keyword_context_from_sample(keywords, dirname, desc):
+  """Return info for expanding a sample into a template.
+
+  Sample may not be about a specific sample.
+
+  Args:
+    keywords: list of string, list of keywords for the given api.
+    dirname: string, directory name of the sample.
+    desc: string, long description of the sample.
+
+  Returns:
+    A dictionary of values useful for template expansion.
+  """
+  context = {
+      'keywords': keywords,
+      'dir': dirname,
+      'dir_escaped': dirname.replace('/', '%2F'),
+      'desc': wiki_escape(desc),
+      }
+  return context
+
+
+def scan_readme_files(dirname):
+  """Scans all subdirs of dirname for README files.
+
+  Args:
+    dirname: string, name of directory to walk.
+
+  Returns:
+    (samples, keyword_set): list of information about all samples, the union
+      of all keywords found.
+  """
+  samples = []
+  keyword_set = set()
+
+  for root, dirs, files in os.walk(dirname):
+    if 'README' in files:
+      filename = os.path.join(root, 'README')
+      with open(filename, 'r') as f:
+        content = f.read()
+        lines = content.splitlines()
+        desc = ' '.join(itertools.takewhile(lambda x: x, lines))
+        api = get_lines('api', lines)
+        keywords = get_lines('keywords', lines)
+
+        for k in keywords:
+          if k not in KEYWORDS:
+            raise ValueError(
+                '%s is not a valid keyword in file %s' % (k, filename))
+        keyword_set.update(keywords)
+        if not api:
+          api = [None]
+        samples.append((api[0], keywords, root[1:], desc))
+
+  samples.sort()
+
+  return samples, keyword_set
+
+
+def main():
+  # Get all the information we need out of the README files in the samples.
+  samples, keyword_set = scan_readme_files('./samples')
+
+  # Now build a wiki page with all that information. Accumulate all the
+  # information as string to be concatenated when were done.
+  page = ['<wiki:toc max_depth="3" />\n= Samples By API =\n']
+
+  # All the samples, grouped by API.
+  current_api = None
+  for api, keywords, dirname, desc in samples:
+    context = context_from_sample(api, keywords, dirname, desc)
+    if context is None:
+      continue
+    if current_api != api:
+      page.append("""
+=== %(api_icon)s %(api_name)s ===
+
+%(api_desc)s
+
+Documentation for the %(api_name)s in [http://api-python-client-doc.appspot.com/%(api)s/%(version)s  PyDoc] 
+
+""" % context)
+      current_api = api
+
+    page.append('|| [http://code.google.com/p/google-api-python-client/source/browse/#hg%(dir_escaped)s %(dir)s] || %(desc)s ||\n' % context)
+
+  # Now group the samples by keywords.
+  for keyword, keyword_name in KEYWORDS.iteritems():
+    if keyword not in keyword_set:
+      continue
+    page.append('\n= %s Samples =\n\n' % keyword_name)
+    page.append('<table border=1 cellspacing=0 cellpadding=8px>\n')
+    for _, keywords, dirname, desc in samples:
+      context = keyword_context_from_sample(keywords, dirname, desc)
+      if keyword not in keywords:
+        continue
+      page.append("""
+<tr>
+  <td>[http://code.google.com/p/google-api-python-client/source/browse/#hg%(dir_escaped)s %(dir)s] </td>
+  <td> %(desc)s </td>
+</tr>""" % context)
+    page.append('</table>\n')
+
+  print ''.join(page)
+
+
+if __name__ == '__main__':
+  main()
diff --git a/samples/adexchangebuyer/README b/samples/adexchangebuyer/README
new file mode 100644
index 0000000..87ad8c4
--- /dev/null
+++ b/samples/adexchangebuyer/README
@@ -0,0 +1,4 @@
+Samples for working with the Ad Exchange Buyer API.
+
+api: adexchangebuyer
+keywords: cmdline
diff --git a/samples/adsense/README b/samples/adsense/README
new file mode 100644
index 0000000..b2f88ac
--- /dev/null
+++ b/samples/adsense/README
@@ -0,0 +1,4 @@
+A collection of command-line samples for the AdSense Management API.
+
+api: adsense
+keywords: cmdline
diff --git a/samples/analytics/README b/samples/analytics/README
new file mode 100644
index 0000000..8b3860a
--- /dev/null
+++ b/samples/analytics/README
@@ -0,0 +1,4 @@
+Command-line samples for producting reports with the Analytics API.
+
+api: analytics
+keywords: cmdline
diff --git a/samples/api-python-client-doc/README b/samples/api-python-client-doc/README
index 835a60c..bcbf475 100644
--- a/samples/api-python-client-doc/README
+++ b/samples/api-python-client-doc/README
@@ -1,6 +1,5 @@
-This sample is the code that drives
+This sample is the code that drives http://api-python-client-doc.appspot.com/
+It is an application that serves up the Python help documentation for each API.
 
-   http://api-python-client-doc.appspot.com/
-
-It is an application that serves up the Python help documentation
-for each API.
+api: discovery
+keywords: appengine
diff --git a/samples/appengine/README b/samples/appengine/README
new file mode 100644
index 0000000..68befe7
--- /dev/null
+++ b/samples/appengine/README
@@ -0,0 +1,5 @@
+Simple Google+ sample that demonstrates the people API. Demontrates
+using the OAuth 2.0 Decorator for Google App Engine applications.
+
+api: plus
+keywords: appengine oauth2
diff --git a/samples/appengine_with_robots/README b/samples/appengine_with_robots/README
new file mode 100644
index 0000000..277df01
--- /dev/null
+++ b/samples/appengine_with_robots/README
@@ -0,0 +1,5 @@
+Sample application that demonstrates how to use AppAssertionCredentials
+to access an API.
+
+api: urlshortener
+keywords: appengine oauth2
diff --git a/samples/audit/README b/samples/audit/README
new file mode 100644
index 0000000..5fcb803
--- /dev/null
+++ b/samples/audit/README
@@ -0,0 +1,4 @@
+Prints the activities for a domain using the Audit API.
+
+api: audit
+keywords: cmdline
diff --git a/samples/blogger/README b/samples/blogger/README
new file mode 100644
index 0000000..3c6dda5
--- /dev/null
+++ b/samples/blogger/README
@@ -0,0 +1,4 @@
+Retrieve the list of blogs and their posts for a user.
+
+api: blogger
+keywords: cmdline
diff --git a/samples/customsearch/README b/samples/customsearch/README
new file mode 100644
index 0000000..144f598
--- /dev/null
+++ b/samples/customsearch/README
@@ -0,0 +1,4 @@
+Search from the command-line.
+
+api: customsearch
+keywords: cmdline
diff --git a/samples/dailymotion/README b/samples/dailymotion/README
new file mode 100644
index 0000000..7139bde
--- /dev/null
+++ b/samples/dailymotion/README
@@ -0,0 +1,3 @@
+Demonstrates using oauth2client against the DailyMotion API.
+
+keywords: oauth2 appengine
diff --git a/samples/django_sample/README b/samples/django_sample/README
new file mode 100644
index 0000000..3aaeda8
--- /dev/null
+++ b/samples/django_sample/README
@@ -0,0 +1,4 @@
+Sample app demonstrating using oauth2client and the Google+ API from Django.
+
+api: plus
+keywords: oauth2 django
diff --git a/samples/groupssettings/README b/samples/groupssettings/README
new file mode 100644
index 0000000..432997a
--- /dev/null
+++ b/samples/groupssettings/README
@@ -0,0 +1,4 @@
+Sample for the Groups Settings API.
+
+api: groupssettings
+keywords: cmdline
diff --git a/samples/gtaskqueue_sample/README b/samples/gtaskqueue_sample/README
index 6535127..f823db0 100644
--- a/samples/gtaskqueue_sample/README
+++ b/samples/gtaskqueue_sample/README
@@ -1,6 +1,9 @@
 This acts as a sample as well as commandline tool for accessing Google TaskQueue
 APIs.
 
+api: taskqueue
+keywords: cmdline
+
 Installation
 ============
 
diff --git a/samples/latitude/README b/samples/latitude/README
new file mode 100644
index 0000000..2737a03
--- /dev/null
+++ b/samples/latitude/README
@@ -0,0 +1,4 @@
+Add a new location via the Latitude API.
+
+api: latitude
+keywords: cmdline
diff --git a/samples/moderator/README b/samples/moderator/README
new file mode 100644
index 0000000..d135bfe
--- /dev/null
+++ b/samples/moderator/README
@@ -0,0 +1,4 @@
+Create new Moderator series and topics via the Moderator API.
+
+api: moderator
+keywords: cmdline
diff --git a/samples/plus/README b/samples/plus/README
new file mode 100644
index 0000000..f9d59de
--- /dev/null
+++ b/samples/plus/README
@@ -0,0 +1,4 @@
+Loop over all a user's activities and print a short snippet.
+
+api: plus
+keywords: cmdline pagination
diff --git a/samples/prediction/README b/samples/prediction/README
index b8c3b42..b0a37b8 100644
--- a/samples/prediction/README
+++ b/samples/prediction/README
@@ -1,5 +1,8 @@
 Before you can run the prediction sample prediction.py, you must load some csv
-formatted data into Google Storage. You can do this by running setup.sh with a 
-bucket/object name of your choice. You must first create the bucket you want to 
-use. This can be done with the gsutil function or via the web UI (Storage 
+formatted data into Google Storage. You can do this by running setup.sh with a
+bucket/object name of your choice. You must first create the bucket you want
+to use. This can be done with the gsutil function or via the web UI (Storage
 Access) in the Google APIs Console.
+
+api: prediction
+keywords: cmdline
diff --git a/samples/searchforshopping/README b/samples/searchforshopping/README
new file mode 100644
index 0000000..aaa1dd0
--- /dev/null
+++ b/samples/searchforshopping/README
@@ -0,0 +1,4 @@
+Samples demonstrating the query capabilities for the Search API for Shopping.
+
+api: shopping
+keywords: cmdline
diff --git a/samples/service_account/README b/samples/service_account/README
new file mode 100644
index 0000000..7662df5
--- /dev/null
+++ b/samples/service_account/README
@@ -0,0 +1,4 @@
+Sample that demonstrates working with Service Accounts.
+
+api: tasks
+keywords: oauth2
diff --git a/samples/tasks_appengine/README b/samples/tasks_appengine/README
index 56da2e8..6888c35 100644
--- a/samples/tasks_appengine/README
+++ b/samples/tasks_appengine/README
@@ -1,2 +1,5 @@
 Sample code for Getting Started with Tasks API on App Engine article.
 http://code.google.com/appengine/articles/python/getting_started_with_tasks_api.html
+
+api: tasks
+keywords: appengine
diff --git a/samples/threadqueue/README b/samples/threadqueue/README
new file mode 100644
index 0000000..4baa99a
--- /dev/null
+++ b/samples/threadqueue/README
@@ -0,0 +1,4 @@
+Demonstrates using threading and thread queues for handling a high volume of requests.
+
+api: moderator
+keywords: cmdline threading
diff --git a/samples/translate/README b/samples/translate/README
new file mode 100644
index 0000000..6bde24c
--- /dev/null
+++ b/samples/translate/README
@@ -0,0 +1,5 @@
+Simple sample for the translate API.
+
+api: translate
+keywords: cmdline
+
diff --git a/samples/tz/README b/samples/tz/README
index ecee023..1afa540 100644
--- a/samples/tz/README
+++ b/samples/tz/README
@@ -4,6 +4,9 @@
 Latitude. To use this application you will need Google
 Latitude running on a mobile device.
 
+api: latitude
+keywords: cmdline
+
 Installation
 ============
   The google-api-python-client library will need to
diff --git a/samples/urlshortener/README b/samples/urlshortener/README
new file mode 100644
index 0000000..a33e8a8
--- /dev/null
+++ b/samples/urlshortener/README
@@ -0,0 +1,4 @@
+Shortens and URL with the URL Shortener API.
+
+api: urlshortener
+keywords: cmdline