Repository: Pylons/pyramid

Issue:
proutes does not show static routes



Discussion:
![captura de tela 2014-02-25 as 15 43 33](https://f.cloud.github.com/assets/1129263/2261283/c7e2ecd4-9e4c-11e3-942a-5deb7fa460a0.png)

There is a difference between static views and a route tagged as static. See the documentation about `config.add_route(..., static=True)` for more info.

in my project is put as config.add_static_view(), he can return an static url... your report bug is about route tagged as view?

I know it's confusing but in pyramid's jargon a "static route" is something that is not used for matching incoming requests, and exist only to help generate urls by name. Static views on the other hand are used to serve up static content from the filesystem.

Since static routes aren't matched against when a request comes in, then I think it's reasonable not to display them in proutes.

Also external routes aren't shown by proutes, which also seems reasonable to me.


Resolving patch:
diff --git a/pyramid/config/routes.py b/pyramid/config/routes.py
--- a/pyramid/config/routes.py
+++ b/pyramid/config/routes.py
@@ -303,6 +303,8 @@ def add_route(self,
         # check for an external route; an external route is one which is
         # is a full url (e.g. 'http://example.com/{id}')
         parsed = urlparse.urlparse(pattern)
+        external_url = pattern
+
         if parsed.hostname:
             pattern = parsed.path
 
@@ -357,6 +359,10 @@ def external_url_pregenerator(request, elements, kw):
         intr['pregenerator'] = pregenerator
         intr['static'] = static
         intr['use_global_views'] = use_global_views
+
+        if static is True:
+            intr['external_url'] = external_url
+
         introspectables.append(intr)
 
         if factory:
diff --git a/pyramid/scripts/proutes.py b/pyramid/scripts/proutes.py
--- a/pyramid/scripts/proutes.py
+++ b/pyramid/scripts/proutes.py
@@ -1,12 +1,26 @@
+import fnmatch
 import optparse
 import sys
 import textwrap
+import re
 
 from pyramid.paster import bootstrap
+from pyramid.compat import (string_types, configparser)
+from pyramid.interfaces import (
+    IRouteRequest,
+    IViewClassifier,
+    IView,
+)
+from pyramid.config import not_
+
 from pyramid.scripts.common import parse_vars
+from pyramid.static import static_view
+from zope.interface import Interface
 
 
 PAD = 3
+ANY_KEY = '*'
+UNKNOWN_KEY = '<unknown>'
 
 
 def main(argv=sys.argv, quiet=False):
@@ -14,6 +28,206 @@ def main(argv=sys.argv, quiet=False):
     return command.run()
 
 
+def _get_pattern(route):
+    pattern = route.pattern
+
+    if not pattern.startswith('/'):
+        pattern = '/%s' % pattern
+    return pattern
+
+
+def _get_print_format(fmt, max_name, max_pattern, max_view, max_method):
+    print_fmt = ''
+    max_map = {
+        'name': max_name,
+        'pattern': max_pattern,
+        'view': max_view,
+        'method': max_method,
+    }
+    sizes = []
+
+    for index, col in enumerate(fmt):
+        size = max_map[col] + PAD
+        print_fmt += '{{%s: <{%s}}} ' % (col, index)
+        sizes.append(size)
+
+    return print_fmt.format(*sizes)
+
+
+def _get_request_methods(route_request_methods, view_request_methods):
+    excludes = set()
+
+    if route_request_methods:
+        route_request_methods = set(route_request_methods)
+
+    if view_request_methods:
+        view_request_methods = set(view_request_methods)
+
+        for method in view_request_methods.copy():
+            if method.startswith('!'):
+                view_request_methods.remove(method)
+                excludes.add(method[1:])
+
+    has_route_methods = route_request_methods is not None
+    has_view_methods = len(view_request_methods) > 0
+    has_methods = has_route_methods or has_view_methods
+
+    if has_route_methods is False and has_view_methods is False:
+        request_methods = [ANY_KEY]
+    elif has_route_methods is False and has_view_methods is True:
+        request_methods = view_request_methods
+    elif has_route_methods is True and has_view_methods is False:
+        request_methods = route_request_methods
+    else:
+        request_methods = route_request_methods.intersection(
+            view_request_methods
+        )
+
+    request_methods = set(request_methods).difference(excludes)
+
+    if has_methods and not request_methods:
+        request_methods = '<route mismatch>'
+    elif request_methods:
+        if excludes and request_methods == set([ANY_KEY]):
+            for exclude in excludes:
+                request_methods.add('!%s' % exclude)
+
+        request_methods = ','.join(sorted(request_methods))
+
+    return request_methods
+
+
+def _get_view_module(view_callable):
+    if view_callable is None:
+        return UNKNOWN_KEY
+
+    if hasattr(view_callable, '__name__'):
+        if hasattr(view_callable, '__original_view__'):
+            original_view = view_callable.__original_view__
+        else:
+            original_view = None
+
+        if isinstance(original_view, static_view):
+            if original_view.package_name is not None:
+                return '%s:%s' % (
+                    original_view.package_name,
+                    original_view.docroot
+                )
+            else:
+                return original_view.docroot
+        else:
+            view_name = view_callable.__name__
+    else:
+        # Currently only MultiView hits this,
+        # we could just not run _get_view_module
+        # for them and remove this logic
+        view_name = str(view_callable)
+
+    view_module = '%s.%s' % (
+        view_callable.__module__,
+        view_name,
+    )
+
+    # If pyramid wraps something in wsgiapp or wsgiapp2 decorators
+    # that is currently returned as pyramid.router.decorator, lets
+    # hack a nice name in:
+    if view_module == 'pyramid.router.decorator':
+        view_module = '<wsgiapp>'
+
+    return view_module
+
+
+def get_route_data(route, registry):
+    pattern = _get_pattern(route)
+
+    request_iface = registry.queryUtility(
+        IRouteRequest,
+        name=route.name
+    )
+
+    route_request_methods = None
+    view_request_methods_order = []
+    view_request_methods = {}
+    view_callable = None
+
+    route_intr = registry.introspector.get(
+        'routes', route.name
+    )
+
+    if request_iface is None:
+        return [
+            (route.name, _get_pattern(route), UNKNOWN_KEY, ANY_KEY)
+        ]
+
+    view_callable = registry.adapters.lookup(
+        (IViewClassifier, request_iface, Interface),
+        IView,
+        name='',
+        default=None
+    )
+    view_module = _get_view_module(view_callable)
+
+    # Introspectables can be turned off, so there could be a chance
+    # that we have no `route_intr` but we do have a route + callable
+    if route_intr is None:
+        view_request_methods[view_module] = []
+        view_request_methods_order.append(view_module)
+    else:
+        if route_intr.get('static', False) is True:
+            return [
+                (route.name, route_intr['external_url'], UNKNOWN_KEY, ANY_KEY)
+            ]
+
+
+        route_request_methods = route_intr['request_methods']
+        view_intr = registry.introspector.related(route_intr)
+
+        if view_intr:
+            for view in view_intr:
+                request_method = view.get('request_methods')
+
+                if request_method is not None:
+                    view_callable = view['callable']
+                    view_module = _get_view_module(view_callable)
+
+                    if view_module not in view_request_methods:
+                        view_request_methods[view_module] = []
+                        view_request_methods_order.append(view_module)
+
+                    if isinstance(request_method, string_types):
+                        request_method = (request_method,)
+                    elif isinstance(request_method, not_):
+                        request_method = ('!%s' % request_method.value,)
+
+                    view_request_methods[view_module].extend(request_method)
+                else:
+                    if view_module not in view_request_methods:
+                        view_request_methods[view_module] = []
+                        view_request_methods_order.append(view_module)
+
+        else:
+            view_request_methods[view_module] = []
+            view_request_methods_order.append(view_module)
+
+    final_routes = []
+
+    for view_module in view_request_methods_order:
+        methods = view_request_methods[view_module]
+        request_methods = _get_request_methods(
+            route_request_methods,
+            methods
+        )
+
+        final_routes.append((
+            route.name,
+            pattern,
+            view_module,
+            request_methods,
+        ))
+
+    return final_routes
+
+
 class PRoutesCommand(object):
     description = """\
     Print all URL dispatch routes used by a Pyramid application in the
@@ -30,111 +244,153 @@ class PRoutesCommand(object):
     bootstrap = (bootstrap,)
     stdout = sys.stdout
     usage = '%prog config_uri'
-
+    ConfigParser = configparser.ConfigParser # testing
     parser = optparse.OptionParser(
         usage,
         description=textwrap.dedent(description)
-        )
+    )
+    parser.add_option('-g', '--glob',
+                      action='store', type='string', dest='glob',
+                      default='', help='Display routes matching glob pattern')
+
+    parser.add_option('-f', '--format',
+                      action='store', type='string', dest='format',
+                      default='', help=('Choose which columns to display, this '
+                                        'will override the format key in the '
+                                        '[proutes] ini section'))
 
     def __init__(self, argv, quiet=False):
         self.options, self.args = self.parser.parse_args(argv[1:])
         self.quiet = quiet
+        self.available_formats = [
+            'name', 'pattern', 'view', 'method'
+        ]
+        self.column_format = self.available_formats
+
+    def validate_formats(self, formats):
+        invalid_formats = []
+        for fmt in formats:
+            if fmt not in self.available_formats:
+                invalid_formats.append(fmt)
+
+        msg = (
+            'You provided invalid formats %s, '
+            'Available formats are %s'
+        )
 
-    def _get_mapper(self, registry):
-        from pyramid.config import Configurator
-        config = Configurator(registry = registry)
-        return config.get_routes_mapper()
+        if invalid_formats:
+            msg = msg % (invalid_formats, self.available_formats)
+            self.out(msg)
+            return False
+
+        return True
+
+    def proutes_file_config(self, filename):
+        config = self.ConfigParser()
+        config.read(filename)
+        try:
+            items = config.items('proutes')
+            for k, v in items:
+                if 'format' == k:
+                    cols = re.split(r'[,|\s|\n]*', v)
+                    self.column_format = [x.strip() for x in cols]
 
-    def out(self, msg): # pragma: no cover
+        except configparser.NoSectionError:
+            return
+
+    def out(self, msg):  # pragma: no cover
         if not self.quiet:
             print(msg)
 
+    def _get_mapper(self, registry):
+        from pyramid.config import Configurator
+        config = Configurator(registry=registry)
+        return config.get_routes_mapper()
+
     def run(self, quiet=False):
         if not self.args:
             self.out('requires a config file argument')
             return 2
 
-        from pyramid.interfaces import IRouteRequest
-        from pyramid.interfaces import IViewClassifier
-        from pyramid.interfaces import IView
-        from pyramid.interfaces import IMultiView
-
-        from zope.interface import Interface
         config_uri = self.args[0]
-
         env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:]))
         registry = env['registry']
         mapper = self._get_mapper(registry)
-        if mapper is not None:
-            mapped_routes = [('Name', 'Pattern', 'View')]
-
-            max_name = len('Name')
-            max_pattern = len('Pattern')
-            max_view = len('View')
-
-            routes = mapper.get_routes()
-
-            if not routes:
-                return 0
-
-            mapped_routes.append((
-                '-' * max_name,
-                '-' * max_pattern,
-                '-' * max_view,
-            ))
-
-            for route in routes:
-                pattern = route.pattern
-                if not pattern.startswith('/'):
-                    pattern = '/' + pattern
-                request_iface = registry.queryUtility(IRouteRequest,
-                                                      name=route.name)
-                view_callable = None
-
-                if (request_iface is None) or (route.factory is not None):
-                    view_callable = '<unknown>'
-                else:
-                    view_callable = registry.adapters.lookup(
-                        (IViewClassifier, request_iface, Interface),
-                        IView, name='', default=None)
-
-                    if view_callable is not None:
-                        if IMultiView.providedBy(view_callable):
-                            view_callables = [
-                                x[1] for x in view_callable.views
-                            ]
-                        else:
-                            view_callables = [view_callable]
-
-                        for view_func in view_callables:
-                            view_callable = '%s.%s' % (
-                                view_func.__module__,
-                                view_func.__name__,
-                            )
-                    else:
-                        view_callable = str(None)
-
-                if len(route.name) > max_name:
-                    max_name = len(route.name)
+
+        self.proutes_file_config(config_uri)
+
+        if self.options.format:
+            columns = self.options.format.split(',')
+            self.column_format = [x.strip() for x in columns]
+
+        is_valid = self.validate_formats(self.column_format)
+
+        if is_valid is False:
+            return 2
+
+        if mapper is None:
+            return 0
+
+        max_name = len('Name')
+        max_pattern = len('Pattern')
+        max_view = len('View')
+        max_method = len('Method')
+
+        routes = mapper.get_routes(include_static=True)
+
+        if len(routes) == 0:
+            return 0
+
+        mapped_routes = [{
+            'name': 'Name',
+            'pattern': 'Pattern',
+            'view': 'View',
+            'method': 'Method'
+        },{
+            'name': '----',
+            'pattern': '-------',
+            'view': '----',
+            'method': '------'
+        }]
+
+        for route in routes:
+            route_data = get_route_data(route, registry)
+
+            for name, pattern, view, method in route_data:
+                if self.options.glob:
+                    match = (fnmatch.fnmatch(name, self.options.glob) or
+                             fnmatch.fnmatch(pattern, self.options.glob))
+                    if not match:
+                        continue
+
+                if len(name) > max_name:
+                    max_name = len(name)
 
                 if len(pattern) > max_pattern:
                     max_pattern = len(pattern)
 
-                if len(view_callable) > max_view:
-                    max_view = len(view_callable)
+                if len(view) > max_view:
+                    max_view = len(view)
 
-                mapped_routes.append((route.name, pattern, view_callable))
+                if len(method) > max_method:
+                    max_method = len(method)
 
-            fmt = '%-{0}s %-{1}s %-{2}s'.format(
-                max_name + PAD,
-                max_pattern + PAD,
-                max_view + PAD,
-            )
+                mapped_routes.append({
+                    'name': name,
+                    'pattern': pattern,
+                    'view': view,
+                    'method': method
+                })
 
-            for route_data in mapped_routes:
-                self.out(fmt % route_data)
+        fmt = _get_print_format(
+            self.column_format, max_name, max_pattern, max_view, max_method
+        )
+
+        for route in mapped_routes:
+            self.out(fmt.format(**route))
 
         return 0
 
-if __name__ == '__main__': # pragma: no cover
+
+if __name__ == '__main__':  # pragma: no cover
     sys.exit(main() or 0)
diff --git a/pyramid/urldispatch.py b/pyramid/urldispatch.py
--- a/pyramid/urldispatch.py
+++ b/pyramid/urldispatch.py
@@ -42,12 +42,17 @@ def __init__(self, name, pattern, factory=None, predicates=(),
 class RoutesMapper(object):
     def __init__(self):
         self.routelist = []
+        self.static_routes = []
+
         self.routes = {}
 
     def has_routes(self):
         return bool(self.routelist)
 
-    def get_routes(self):
+    def get_routes(self, include_static=False):
+        if include_static is True:
+            return self.routelist + self.static_routes
+
         return self.routelist
 
     def get_route(self, name):
@@ -59,9 +64,13 @@ def connect(self, name, pattern, factory=None, predicates=(),
             oldroute = self.routes[name]
             if oldroute in self.routelist:
                 self.routelist.remove(oldroute)
+
         route = Route(name, pattern, factory, predicates, pregenerator)
         if not static:
             self.routelist.append(route)
+        else:
+            self.static_routes.append(route)
+
         self.routes[name] = route
         return route
 


Test patch:
diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py
--- a/pyramid/tests/test_scripts/dummy.py
+++ b/pyramid/tests/test_scripts/dummy.py
@@ -60,7 +60,7 @@ class DummyMapper(object):
     def __init__(self, *routes):
         self.routes = routes
 
-    def get_routes(self):
+    def get_routes(self, include_static=False):
         return self.routes
 
 class DummyRoute(object):
diff --git a/pyramid/tests/test_scripts/test_proutes.py b/pyramid/tests/test_scripts/test_proutes.py
--- a/pyramid/tests/test_scripts/test_proutes.py
+++ b/pyramid/tests/test_scripts/test_proutes.py
@@ -1,6 +1,16 @@
 import unittest
 from pyramid.tests.test_scripts import dummy
 
+
+class DummyIntrospector(object):
+    def __init__(self):
+        self.relations = {}
+        self.introspectables = {}
+
+    def get(self, name, discrim):
+        pass
+
+
 class TestPRoutesCommand(unittest.TestCase):
     def _getTargetClass(self):
         from pyramid.scripts.proutes import PRoutesCommand
@@ -10,8 +20,20 @@ def _makeOne(self):
         cmd = self._getTargetClass()([])
         cmd.bootstrap = (dummy.DummyBootstrap(),)
         cmd.args = ('/foo/bar/myapp.ini#myapp',)
+
         return cmd
 
+    def _makeRegistry(self):
+        from pyramid.registry import Registry
+        registry = Registry()
+        registry.introspector = DummyIntrospector()
+        return registry
+
+    def _makeConfig(self, *arg, **kw):
+        from pyramid.config import Configurator
+        config = Configurator(*arg, **kw)
+        return config
+
     def test_good_args(self):
         cmd = self._getTargetClass()([])
         cmd.bootstrap = (dummy.DummyBootstrap(),)
@@ -19,6 +41,8 @@ def test_good_args(self):
         route = dummy.DummyRoute('a', '/a')
         mapper = dummy.DummyMapper(route)
         cmd._get_mapper = lambda *arg: mapper
+        registry = self._makeRegistry()
+        cmd.bootstrap = (dummy.DummyBootstrap(registry=registry),)
         L = []
         cmd.out = lambda msg: L.append(msg)
         cmd.run()
@@ -58,12 +82,15 @@ def test_single_route_no_route_registered(self):
         route = dummy.DummyRoute('a', '/a')
         mapper = dummy.DummyMapper(route)
         command._get_mapper = lambda *arg: mapper
+        registry = self._makeRegistry()
+        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
+
         L = []
         command.out = L.append
         result = command.run()
         self.assertEqual(result, 0)
         self.assertEqual(len(L), 3)
-        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>'])
+        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '*'])
 
     def test_route_with_no_slash_prefix(self):
         command = self._makeOne()
@@ -72,16 +99,18 @@ def test_route_with_no_slash_prefix(self):
         command._get_mapper = lambda *arg: mapper
         L = []
         command.out = L.append
+        registry = self._makeRegistry()
+        command.bootstrap = (dummy.DummyBootstrap(registry=registry),)
         result = command.run()
         self.assertEqual(result, 0)
         self.assertEqual(len(L), 3)
-        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>'])
+        self.assertEqual(L[-1].split(), ['a', '/a', '<unknown>', '*'])
 
     def test_single_route_no_views_registered(self):
         from zope.interface import Interface
-        from pyramid.registry import Registry
         from pyramid.interfaces import IRouteRequest
-        registry = Registry()
+        registry = self._makeRegistry()
+
         def view():pass
         class IMyRoute(Interface):
             pass
@@ -96,15 +125,15 @@ class IMyRoute(Interface):
         result = command.run()
         self.assertEqual(result, 0)
         self.assertEqual(len(L), 3)
-        self.assertEqual(L[-1].split()[:3], ['a', '/a', 'None'])
+        self.assertEqual(L[-1].split()[:3], ['a', '/a', '<unknown>'])
 
     def test_single_route_one_view_registered(self):
         from zope.interface import Interface
-        from pyramid.registry import Registry
         from pyramid.interfaces import IRouteRequest
         from pyramid.interfaces import IViewClassifier
         from pyramid.interfaces import IView
-        registry = Registry()
+        registry = self._makeRegistry()
+
         def view():pass
         class IMyRoute(Interface):
             pass
@@ -130,11 +159,11 @@ class IMyRoute(Interface):
 
     def test_one_route_with_long_name_one_view_registered(self):
         from zope.interface import Interface
-        from pyramid.registry import Registry
         from pyramid.interfaces import IRouteRequest
         from pyramid.interfaces import IViewClassifier
         from pyramid.interfaces import IView
-        registry = Registry()
+        registry = self._makeRegistry()
+
         def view():pass
 
         class IMyRoute(Interface):
@@ -172,11 +201,11 @@ class IMyRoute(Interface):
 
     def test_single_route_one_view_registered_with_factory(self):
         from zope.interface import Interface
-        from pyramid.registry import Registry
         from pyramid.interfaces import IRouteRequest
         from pyramid.interfaces import IViewClassifier
         from pyramid.interfaces import IView
-        registry = Registry()
+        registry = self._makeRegistry()
+
         def view():pass
         class IMyRoot(Interface):
             pass
@@ -201,12 +230,11 @@ def factory(request): pass
 
     def test_single_route_multiview_registered(self):
         from zope.interface import Interface
-        from pyramid.registry import Registry
         from pyramid.interfaces import IRouteRequest
         from pyramid.interfaces import IViewClassifier
         from pyramid.interfaces import IMultiView
 
-        registry = Registry()
+        registry = self._makeRegistry()
 
         def view(): pass
 
@@ -235,19 +263,494 @@ class IMyRoute(Interface):
         self.assertEqual(result, 0)
         self.assertEqual(len(L), 3)
         compare_to = L[-1].split()[:3]
+        view_module = 'pyramid.tests.test_scripts.dummy'
+        view_str = '<pyramid.tests.test_scripts.dummy.DummyMultiView'
+        final = '%s.%s' % (view_module, view_str)
+
         self.assertEqual(
             compare_to,
-            ['a', '/a', 'pyramid.tests.test_scripts.test_proutes.view']
+            ['a', '/a', final]
         )
 
     def test__get_mapper(self):
-        from pyramid.registry import Registry
         from pyramid.urldispatch import RoutesMapper
         command = self._makeOne()
-        registry = Registry()
+        registry = self._makeRegistry()
+
         result = command._get_mapper(registry)
         self.assertEqual(result.__class__, RoutesMapper)
 
+    def test_one_route_all_methods_view_only_post(self):
+        from pyramid.renderers import null_renderer as nr
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method='POST'
+        )
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', '/a/b',
+            'pyramid.tests.test_scripts.test_proutes.view1', 'POST'
+        ]
+        self.assertEqual(compare_to, expected)
+
+    def test_one_route_only_post_view_all_methods(self):
+        from pyramid.renderers import null_renderer as nr
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b', request_method='POST')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+        )
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', '/a/b',
+            'pyramid.tests.test_scripts.test_proutes.view1', 'POST'
+        ]
+        self.assertEqual(compare_to, expected)
+
+    def test_one_route_only_post_view_post_and_get(self):
+        from pyramid.renderers import null_renderer as nr
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b', request_method='POST')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method=('POST', 'GET')
+        )
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', '/a/b',
+            'pyramid.tests.test_scripts.test_proutes.view1', 'POST'
+        ]
+        self.assertEqual(compare_to, expected)
+
+    def test_route_request_method_mismatch(self):
+        from pyramid.renderers import null_renderer as nr
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b', request_method='POST')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method='GET'
+        )
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', '/a/b',
+            'pyramid.tests.test_scripts.test_proutes.view1',
+            '<route', 'mismatch>'
+        ]
+        self.assertEqual(compare_to, expected)
+
+    def test_route_static_views(self):
+        from pyramid.renderers import null_renderer as nr
+        config = self._makeConfig(autocommit=True)
+        config.add_static_view('static', 'static', cache_max_age=3600)
+        config.add_static_view(name='static2', path='/var/www/static')
+        config.add_static_view(
+            name='pyramid_scaffold',
+            path='pyramid:scaffolds/starter/+package+/static'
+        )
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 5)
+
+        expected = [
+            ['__static/', '/static/*subpath',
+             'pyramid.tests.test_scripts:static/', '*'],
+            ['__static2/', '/static2/*subpath', '/var/www/static/', '*'],
+            ['__pyramid_scaffold/', '/pyramid_scaffold/*subpath',
+             'pyramid:scaffolds/starter/+package+/static/',  '*'],
+        ]
+
+        for index, line in enumerate(L[2:]):
+            data = line.split()
+            self.assertEqual(data, expected[index])
+
+    def test_route_no_view(self):
+        from pyramid.renderers import null_renderer as nr
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b', request_method='POST')
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', '/a/b',
+            '<unknown>',
+            'POST',
+        ]
+        self.assertEqual(compare_to, expected)
+
+    def test_route_as_wsgiapp(self):
+        from pyramid.wsgi import wsgiapp2
+
+        config1 = self._makeConfig(autocommit=True)
+        def view1(context, request): return 'view1'
+        config1.add_route('foo', '/a/b', request_method='POST')
+        config1.add_view(view=view1, route_name='foo')
+
+        config2 = self._makeConfig(autocommit=True)
+        config2.add_route('foo', '/a/b', request_method='POST')
+        config2.add_view(
+            wsgiapp2(config1.make_wsgi_app()),
+            route_name='foo',
+        )
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config2.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', '/a/b',
+            '<wsgiapp>',
+            'POST',
+        ]
+        self.assertEqual(compare_to, expected)
+
+    def test_route_is_get_view_request_method_not_post(self):
+        from pyramid.renderers import null_renderer as nr
+        from pyramid.config import not_
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b', request_method='GET')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method=not_('POST')
+        )
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', '/a/b',
+            'pyramid.tests.test_scripts.test_proutes.view1',
+            'GET'
+        ]
+        self.assertEqual(compare_to, expected)
+
+    def test_view_request_method_not_post(self):
+        from pyramid.renderers import null_renderer as nr
+        from pyramid.config import not_
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method=not_('POST')
+        )
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', '/a/b',
+            'pyramid.tests.test_scripts.test_proutes.view1',
+            '!POST,*'
+        ]
+        self.assertEqual(compare_to, expected)
+
+    def test_view_glob(self):
+        from pyramid.renderers import null_renderer as nr
+        from pyramid.config import not_
+
+        def view1(context, request): return 'view1'
+        def view2(context, request): return 'view2'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method=not_('POST')
+        )
+
+        config.add_route('bar', '/b/a')
+        config.add_view(
+            route_name='bar',
+            view=view2,
+            renderer=nr,
+            request_method=not_('POST')
+        )
+
+        command = self._makeOne()
+        command.options.glob = '*foo*'
+
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', '/a/b',
+            'pyramid.tests.test_scripts.test_proutes.view1',
+            '!POST,*'
+        ]
+        self.assertEqual(compare_to, expected)
+
+    def test_good_format(self):
+        from pyramid.renderers import null_renderer as nr
+        from pyramid.config import not_
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method=not_('POST')
+        )
+
+        command = self._makeOne()
+        command.options.glob = '*foo*'
+        command.options.format = 'method,name'
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = ['!POST,*', 'foo']
+
+        self.assertEqual(compare_to, expected)
+        self.assertEqual(L[0].split(), ['Method', 'Name'])
+
+    def test_bad_format(self):
+        from pyramid.renderers import null_renderer as nr
+        from pyramid.config import not_
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method=not_('POST')
+        )
+
+        command = self._makeOne()
+        command.options.glob = '*foo*'
+        command.options.format = 'predicates,name,pattern'
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        expected = (
+            "You provided invalid formats ['predicates'], "
+            "Available formats are ['name', 'pattern', 'view', 'method']"
+        )
+        result = command.run()
+        self.assertEqual(result, 2)
+        self.assertEqual(L[0], expected)
+
+    def test_config_format_ini_newlines(self):
+        from pyramid.renderers import null_renderer as nr
+        from pyramid.config import not_
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method=not_('POST')
+        )
+
+        command = self._makeOne()
+
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        config_factory = dummy.DummyConfigParserFactory()
+        command.ConfigParser = config_factory
+        config_factory.items = [('format', 'method\nname')]
+
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = ['!POST,*', 'foo']
+
+        self.assertEqual(compare_to, expected)
+        self.assertEqual(L[0].split(), ['Method', 'Name'])
+
+    def test_config_format_ini_spaces(self):
+        from pyramid.renderers import null_renderer as nr
+        from pyramid.config import not_
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method=not_('POST')
+        )
+
+        command = self._makeOne()
+
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        config_factory = dummy.DummyConfigParserFactory()
+        command.ConfigParser = config_factory
+        config_factory.items = [('format', 'method name')]
+
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = ['!POST,*', 'foo']
+
+        self.assertEqual(compare_to, expected)
+        self.assertEqual(L[0].split(), ['Method', 'Name'])
+
+    def test_config_format_ini_commas(self):
+        from pyramid.renderers import null_renderer as nr
+        from pyramid.config import not_
+
+        def view1(context, request): return 'view1'
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', '/a/b')
+        config.add_view(
+            route_name='foo',
+            view=view1,
+            renderer=nr,
+            request_method=not_('POST')
+        )
+
+        command = self._makeOne()
+
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        config_factory = dummy.DummyConfigParserFactory()
+        command.ConfigParser = config_factory
+        config_factory.items = [('format', 'method,name')]
+
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = ['!POST,*', 'foo']
+
+        self.assertEqual(compare_to, expected)
+        self.assertEqual(L[0].split(), ['Method', 'Name'])
+
+    def test_static_routes_included_in_list(self):
+        from pyramid.renderers import null_renderer as nr
+
+        config = self._makeConfig(autocommit=True)
+        config.add_route('foo', 'http://example.com/bar.aspx', static=True)
+
+        command = self._makeOne()
+        L = []
+        command.out = L.append
+        command.bootstrap = (dummy.DummyBootstrap(registry=config.registry),)
+        result = command.run()
+        self.assertEqual(result, 0)
+        self.assertEqual(len(L), 3)
+        compare_to = L[-1].split()
+        expected = [
+            'foo', 'http://example.com/bar.aspx',
+            '<unknown>', '*',
+        ]
+        self.assertEqual(compare_to, expected)
+
 class Test_main(unittest.TestCase):
     def _callFUT(self, argv):
         from pyramid.scripts.proutes import main