First pass at adding in pagination
diff --git a/Makefile b/Makefile
index debf9c3..12c6ff9 100644
--- a/Makefile
+++ b/Makefile
@@ -3,3 +3,6 @@
 
 test:
 	python runtests.py
+
+skeletons:
+	python discovery_extras.py tests/data/buzz.json tests/data/latitude.json tests/data/moderator.json
diff --git a/apiclient/contrib/buzz/future.json b/apiclient/contrib/buzz/future.json
new file mode 100644
index 0000000..621ee52
--- /dev/null
+++ b/apiclient/contrib/buzz/future.json
@@ -0,0 +1,89 @@
+{
+  "data": {
+    "buzz": {
+      "v1": {
+        "baseUrl": "https://www.googleapis.com/", 
+        "resources": {
+          "activities": {
+            "methods": {
+              "delete": {}, 
+              "get": {}, 
+              "insert": {}, 
+              "list": {
+                "next": {
+                  "type": "uri",
+                  "location": ["links", "next", 0, "href"]
+                }
+              }, 
+              "search": {
+                "next": {
+                  "type": "uri",
+                  "location": ["links", "next", 0, "href"]
+                }
+              }, 
+              "update": {}
+            }
+          }, 
+          "comments": {
+            "methods": {
+              "delete": {}, 
+              "get": {}, 
+              "insert": {}, 
+              "list": {}, 
+              "update": {}
+            }
+          }, 
+          "feeds": {
+            "methods": {
+              "delete": {}, 
+              "insert": {}, 
+              "list": {}, 
+              "update": {}
+            }
+          }, 
+          "groups": {
+            "methods": {
+              "delete": {}, 
+              "get": {}, 
+              "insert": {}, 
+              "list": {
+                "next": {
+                  "type": "uri",
+                  "location": ["links", "next", 0, "href"]
+                }
+              }, 
+              "update": {}
+            }
+          }, 
+          "people": {
+            "methods": {
+              "delete": {}, 
+              "get": {}, 
+              "liked": {}, 
+              "list": {}, 
+              "relatedToUri": {}, 
+              "reshared": {}, 
+              "search": {}, 
+              "update": {}
+            }
+          }, 
+          "photos": {
+            "methods": {
+              "insert": {}
+            }
+          }, 
+          "related": {
+            "methods": {
+              "list": {}
+            }
+          }, 
+          "search": {
+            "methods": {
+              "extractPeople": {}
+            }
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/apiclient/contrib/latitude/future.json b/apiclient/contrib/latitude/future.json
new file mode 100644
index 0000000..657f6e8
--- /dev/null
+++ b/apiclient/contrib/latitude/future.json
@@ -0,0 +1,26 @@
+{
+  "data": {
+    "latitude": {
+      "v1": {
+        "baseUrl": "https://www.googleapis.com/", 
+        "resources": {
+          "currentLocation": {
+            "methods": {
+              "delete": {}, 
+              "get": {}, 
+              "insert": {}
+            }
+          }, 
+          "location": {
+            "methods": {
+              "delete": {}, 
+              "get": {}, 
+              "insert": {}, 
+              "list": {}
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apiclient/contrib/moderator/future.json b/apiclient/contrib/moderator/future.json
new file mode 100644
index 0000000..3d46f97
--- /dev/null
+++ b/apiclient/contrib/moderator/future.json
@@ -0,0 +1,60 @@
+{
+  "data": {
+    "moderator": {
+      "v1": {
+        "baseUrl": "https://www.googleapis.com/", 
+        "resources": {
+          "profiles": {
+            "methods": {
+              "get": {}, 
+              "update": {}
+            }
+          }, 
+          "responses": {
+            "methods": {
+              "insert": {}, 
+              "list": {}
+            }
+          }, 
+          "series": {
+            "methods": {
+              "get": {}, 
+              "insert": {}, 
+              "list": {}, 
+              "update": {}
+            }
+          }, 
+          "submissions": {
+            "methods": {
+              "get": {}, 
+              "insert": {}, 
+              "list": {}
+            }
+          }, 
+          "tags": {
+            "methods": {
+              "delete": {}, 
+              "insert": {}, 
+              "list": {}
+            }
+          }, 
+          "topics": {
+            "methods": {
+              "get": {}, 
+              "insert": {}, 
+              "list": {}
+            }
+          }, 
+          "votes": {
+            "methods": {
+              "get": {}, 
+              "insert": {}, 
+              "list": {}, 
+              "update": {}
+            }
+          }
+        }
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/apiclient/discovery.py b/apiclient/discovery.py
index 3cd2c9f..d9fd085 100644
--- a/apiclient/discovery.py
+++ b/apiclient/discovery.py
@@ -23,17 +23,21 @@
 
 import httplib2
 import logging
+import os
 import re
 import simplejson
-import urlparse
 import uritemplate
+import urlparse
 
 
 class HttpError(Exception):
   pass
 
-DISCOVERY_URI = 'http://www.googleapis.com/discovery/0.1/describe\
-{?api,apiVersion}'
+class UnknownLinkType(Exception):
+  pass
+
+DISCOVERY_URI = ('http://www.googleapis.com/discovery/0.1/describe'
+  '{?api,apiVersion}')
 
 
 def key2method(key):
@@ -107,10 +111,10 @@
         raise HttpError(simplejson.loads(content)['error'])
 
 
-def build(service, version, http=httplib2.Http(),
+def build(serviceName, version, http=httplib2.Http(),
     discoveryServiceUrl=DISCOVERY_URI, auth=None, model=JsonModel()):
   params = {
-      'api': service,
+      'api': serviceName,
       'apiVersion': version
       }
 
@@ -118,7 +122,14 @@
   logging.info('URL being requested: %s' % requested_url)
   resp, content = http.request(requested_url)
   d = simplejson.loads(content)
-  service = d['data'][service][version]
+  service = d['data'][serviceName][version]
+
+  fn = os.path.join(os.path.dirname(__file__), "contrib", serviceName, "future.json")
+  f = file(fn, "r")
+  d = simplejson.load(f)
+  f.close()
+  future = d['data'][serviceName][version]['resources']
+
   base = service['baseUrl']
   resources = service['resources']
 
@@ -130,21 +141,21 @@
       self._baseUrl = base
       self._model = model
 
-  def createMethod(theclass, methodName, methodDesc):
+  def createMethod(theclass, methodName, methodDesc, futureDesc):
 
     def method(self, **kwargs):
       return createResource(self._http, self._baseUrl, self._model,
-          methodName, methodDesc)
+          methodName, methodDesc, futureDesc)
 
     setattr(method, '__doc__', 'A description of how to use this function')
     setattr(theclass, methodName, method)
 
   for methodName, methodDesc in resources.iteritems():
-    createMethod(Service, methodName, methodDesc)
+    createMethod(Service, methodName, methodDesc, future[methodName])
   return Service()
 
 
-def createResource(http, baseUrl, model, resourceName, resourceDesc):
+def createResource(http, baseUrl, model, resourceName, resourceDesc, futureDesc):
 
   class Resource(object):
     """A class for interacting with a resource."""
@@ -154,7 +165,7 @@
       self._baseUrl = baseUrl
       self._model = model
 
-  def createMethod(theclass, methodName, methodDesc):
+  def createMethod(theclass, methodName, methodDesc, futureDesc):
     pathUrl = methodDesc['pathUrl']
     pathUrl = re.sub(r'\{', r'{+', pathUrl)
     httpMethod = methodDesc['httpMethod']
@@ -207,7 +218,7 @@
       headers = {}
       headers, params, query, body = self._model.request(headers, actual_path_params, actual_query_params, body_value)
 
-      expanded_url = uritemplate.expand(pathUrl, params) 
+      expanded_url = uritemplate.expand(pathUrl, params)
       url = urlparse.urljoin(self._baseUrl, expanded_url + query)
 
       logging.info('URL being requested: %s' % url)
@@ -218,12 +229,53 @@
 
     docs = ['A description of how to use this function\n\n']
     for arg in argmap.iterkeys():
-      docs.append('%s - A parameter\n' % arg)
+      required = ""
+      if arg in required_params:
+        required = " (required)"
+      docs.append('%s - A parameter%s\n' % (arg, required))
 
     setattr(method, '__doc__', ''.join(docs))
     setattr(theclass, methodName, method)
 
+  def createNextMethod(theclass, methodName, methodDesc):
+
+    def method(self, previous):
+      """
+      Takes a single argument, 'body', which is the results
+      from the last call, and returns the next set of items
+      in the collection.
+
+      Returns None if there are no more items in
+      the collection.
+      """
+      if methodDesc['type'] != 'uri':
+        raise UnknownLinkType(methodDesc['type'])
+
+      try:
+        p = previous
+        for key in methodDesc['location']:
+          p = p[key]
+        url = p
+      except KeyError:
+        return None
+
+      headers = {}
+      headers, params, query, body = self._model.request(headers, {}, {}, None)
+
+      logging.info('URL being requested: %s' % url)
+      resp, content = self._http.request(url, method='GET', headers=headers)
+
+      return self._model.response(resp, content)
+
+    setattr(theclass, methodName, method)
+
+  # Add basic methods to Resource
   for methodName, methodDesc in resourceDesc['methods'].iteritems():
-    createMethod(Resource, methodName, methodDesc)
+    createMethod(Resource, methodName, methodDesc, futureDesc['methods'].get(methodName, {}))
+
+  # Add <m>_next() methods to Resource
+  for methodName, methodDesc in futureDesc['methods'].iteritems():
+    if 'next' in methodDesc and methodName in resourceDesc['methods']:
+      createNextMethod(Resource, methodName + "_next", methodDesc['next'])
 
   return Resource()
diff --git a/discovery_extras.py b/discovery_extras.py
new file mode 100644
index 0000000..f8c1d30
--- /dev/null
+++ b/discovery_extras.py
@@ -0,0 +1,52 @@
+# Copyright (C) 2010 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.
+
+"""Generate a skeleton discovery extras document.
+
+For the given API, retrieve the discovery document,
+strip out the guts of each method description
+and put :
+"""
+
+__author__ = 'jcgregorio@google.com (Joe Gregorio)'
+
+import os
+import os.path
+import simplejson
+import sys
+
+def main():
+  for filename in sys.argv[1:]:
+    f = file(filename, "r")
+    dis = simplejson.load(f)
+    f.close()
+
+    data = dis['data']
+    api = data[data.keys()[0]]
+    version = api[api.keys()[0]]
+    resources = version['resources']
+    for res_name, res_desc in resources.iteritems():
+      methods = res_desc['methods']
+      for method_name, method_desc in methods.iteritems():
+        methods[method_name] = {}
+    path, basename = os.path.split(filename)
+    newfilename = os.path.join(path, "skel-" + basename)
+    f = file(newfilename, "w")
+    simplejson.dump(dis, f, sort_keys=True, indent=2 * ' ')
+    f.close()
+
+
+if __name__ == '__main__':
+  main()
+
diff --git a/samples/cmdline/three_legged_dance.py b/samples/cmdline/three_legged_dance.py
index cc0ebf2..a6b1377 100644
--- a/samples/cmdline/three_legged_dance.py
+++ b/samples/cmdline/three_legged_dance.py
@@ -17,14 +17,14 @@
 consumer_key = 'anonymous'
 consumer_secret = 'anonymous'
 
-request_token_url = 'https://www.google.com/accounts/OAuthGetRequestToken\
-?domain=anonymous&scope=https://www.googleapis.com/auth/buzz'
+request_token_url = ('https://www.google.com/accounts/OAuthGetRequestToken'
+  '?domain=anonymous&scope=https://www.googleapis.com/auth/buzz')
 
-access_token_url = 'https://www.google.com/accounts/OAuthGetAccessToken\
-?domain=anonymous&scope=https://www.googleapis.com/auth/buzz'
+access_token_url = ('https://www.google.com/accounts/OAuthGetAccessToken'
+  '?domain=anonymous&scope=https://www.googleapis.com/auth/buzz')
 
-authorize_url = 'https://www.google.com/buzz/api/auth/OAuthAuthorizeToken\
-?domain=anonymous&scope=https://www.googleapis.com/auth/buzz'
+authorize_url = ('https://www.google.com/buzz/api/auth/OAuthAuthorizeToken'
+  '?domain=anonymous&scope=https://www.googleapis.com/auth/buzz')
 
 consumer = oauth.Consumer(consumer_key, consumer_secret)
 client = oauth.Client(consumer)
diff --git a/tests/data/buzz.json b/tests/data/buzz.json
index 0b25911..f12a445 100644
--- a/tests/data/buzz.json
+++ b/tests/data/buzz.json
@@ -497,6 +497,26 @@
          }
         }
        },
+       "relatedToUri": {
+        "pathUrl": "buzz/v1/people/{userId}/@related",
+        "rpcName": "buzz.people.relatedToUri",
+        "httpMethod": "POST",
+        "methodType": "rest",
+        "parameters": {
+         "alt": {
+          "parameterType": "query",
+          "required": false
+         },
+         "uri": {
+          "parameterType": "query",
+          "required": false
+         },
+         "hl": {
+          "parameterType": "query",
+          "required": false
+         }
+        }
+       },
        "reshared": {
         "pathUrl": "buzz/v1/activities/{userId}/{scope}/{postId}/{groupId}",
         "rpcName": "buzz.people.reshared",
@@ -722,15 +742,16 @@
          }
         }
        },
-       "insert": {
-        "pathUrl": "buzz/v1/people/{userId}/@groups",
-        "rpcName": "buzz.groups.insert",
-        "httpMethod": "POST",
+       "update": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}/@self",
+        "rpcName": "buzz.groups.update",
+        "httpMethod": "PUT",
         "methodType": "rest",
         "parameters": {
-         "alt": {
-          "parameterType": "query",
-          "required": false
+         "groupId": {
+          "parameterType": "path",
+          "pattern": "[^/]+",
+          "required": true
          },
          "userId": {
           "parameterType": "path",
@@ -743,16 +764,15 @@
          }
         }
        },
-       "update": {
-        "pathUrl": "buzz/v1/people/{userId}/@groups/{groupId}/@self",
-        "rpcName": "buzz.groups.update",
-        "httpMethod": "PUT",
+       "insert": {
+        "pathUrl": "buzz/v1/people/{userId}/@groups",
+        "rpcName": "buzz.groups.insert",
+        "httpMethod": "POST",
         "methodType": "rest",
         "parameters": {
-         "groupId": {
-          "parameterType": "path",
-          "pattern": "[^/]+",
-          "required": true
+         "alt": {
+          "parameterType": "query",
+          "required": false
          },
          "userId": {
           "parameterType": "path",
diff --git a/tests/test_discovery.py b/tests/test_discovery.py
index 6af7d0b..539796f 100644
--- a/tests/test_discovery.py
+++ b/tests/test_discovery.py
@@ -1,6 +1,19 @@
 #!/usr/bin/python2.4
 #
-# Copyright 2010 Google Inc. All Rights Reserved.
+# Copyright 2010 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.
+
 
 """Discovery document tests
 
diff --git a/tests/test_json_model.py b/tests/test_json_model.py
index 7b95d58..7af613f 100644
--- a/tests/test_json_model.py
+++ b/tests/test_json_model.py
@@ -1,10 +1,22 @@
 #!/usr/bin/python2.4
 #
-# Copyright 2010 Google Inc. All Rights Reserved.
+# Copyright 2010 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.
 
-"""Discovery document tests
+"""JSON Model tests
 
-Unit tests for objects created from discovery documents.
+Unit tests for the JSON model.
 """
 
 __author__ = 'jcgregorio@google.com (Joe Gregorio)'