3
# Copyright 2011 Canonical Ltd. This software is licensed under the
4
# GNU Affero General Public License version 3 (see the file LICENSE).
6
"""list-pages -- list the pages that are in Launchpad
8
Prints out a CSV of all of the pages that exist in Launchpad. A "page" is a
9
whole, actual, web page. Something you might actually want to see filling a
10
browser window. Page fragments, portlets etc. are excluded.
12
The output contains the page ID, content interface, layer, template and
13
example URL. Here 'layer' is a clue as to the subdomain.
15
The example URLs will look like::
17
https://launchpad.dev/~<ITeam.name>/+addmember
19
Which means all segments are present, and you can get a real URL by
20
substituting data (in this case, a team name),
24
https://launchpad.dev/[[ITranslator]]/+admin
26
Which means we couldn't actually figure out the proper URL. [[ITranslator]]
27
might actually be made of multiple URL segments,
33
Which means we have no clue how to figure out the URL. This is generally
34
because our cheat objects don't match the app-encoded business logic.
38
# The implementation approach here is fairly simple:
40
# - go through all of the adapters, find the ones that look like they might
42
# - bang the adapter nut against the hardest rock we can find until it cracks
43
# - print the nourishing nutty goodness to stdout
48
from inspect import getmro
51
from zope.app.pagetemplate.simpleviewclass import simple
52
from zope.app.testing.functional import FunctionalTestSetup
53
from zope.component import adapts, getGlobalSiteManager
54
from zope.interface import directlyProvides, implements
55
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
57
from canonical.config import config
58
from canonical.launchpad import pagetitles
59
from canonical.launchpad.scripts import execute_zcml_for_scripts
60
from canonical.launchpad.webapp.interfaces import ICanonicalUrlData
61
from canonical.launchpad.webapp.publisher import canonical_url
64
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
67
def load_zcml(zopeless=False):
68
"""Load ZCML the way we do in the web site."""
69
# We can't load ZCML without generating configuration.
70
config.generate_overrides()
72
execute_zcml_for_scripts()
74
FunctionalTestSetup(os.path.join(ROOT, 'site.zcml')).setUp()
77
def is_page_adapter(adapter):
78
"""Is 'adapter' a page adapter?
80
We figure this out by checking to see whether it is adapting from
81
IDefaultBrowserLayer or one of its subclasses.
83
for interface in adapter.required:
84
if issubclass(interface, IDefaultBrowserLayer):
89
def get_view(adapter):
90
"""Get the view factory associated with 'adapter'."""
91
return adapter.factory
94
def get_template_filename(view):
95
"""Return the template filename associated with 'view'."""
97
filename = view.index.filename
98
except AttributeError:
100
return os.path.abspath(filename)
103
def has_page_title(adapter):
104
"""Does 'adapter' have a page title?
106
We use this to tell if 'adapter' is indeed an adapter for rendering an
107
entire page. The theory goes that if there's a page title associated with
108
something, then it's a page.
110
view = get_view(adapter)
112
template = get_template_filename(view)
115
name = os.path.splitext(os.path.basename(template))[0].replace('-', '_')
116
if (getattr(view, 'page_title', marker) is marker and
117
getattr(pagetitles, name, marker) is marker):
119
return has_html_element(template)
122
def has_html_element(template):
123
return '</html>' in open(template).read()
126
def iter_page_adapters():
127
"""Iterate over adapters for browser:page directives."""
128
gsm = getGlobalSiteManager()
129
return (a for a in gsm.registeredAdapters()
130
if is_page_adapter(a) and has_page_title(a))
133
def iter_url_adapters():
134
gsm = getGlobalSiteManager()
135
return (a for a in gsm.registeredAdapters()
136
if issubclass(a.provided, ICanonicalUrlData))
139
def format_page_adapter(a):
140
"""Format the adapter 'a' for end-user display.
142
:return: A string of the form, "pageid,content,layer,template".
146
bases = getmro(factory)
147
except AttributeError:
149
# Zope, bless it, does all sorts of type shenanigans to obscure the name
150
# of the factory as it appears in our code. (See
151
# z.browserpage.metaconfigure.page and
152
# z.app.pagetemplate.simpleviewclass). Ideally, we'd just construct the
153
# view, but that's hard, since in general they won't construct without an
154
# object that implements the interface they need.
155
bases = [b for b in bases
157
and 'SimpleViewClass' not in b.__name__]
158
template = get_template_filename(get_view(a))
160
template = os.path.relpath(template, ROOT)
161
url = '%s/%s' % (get_example_canonical_url(a.required[0]), a.name)
162
return '%s:%s,%s,%s,%s,%s' % (
165
a.required[0].__name__,
166
a.required[1].__name__,
175
class Whatever(object):
177
def __init__(self, name, interface=None):
179
self._interface = interface
180
if interface is not None:
181
directlyProvides(self, interface)
184
return '<%s>' % (self._name,)
187
return '<%s>' % str(self)
192
def __call__(self, *args, **kwargs):
193
args = map(repr, args)
194
args.extend('%s=%r' % (k, v) for k, v in kwargs.iteritems())
195
# If we're being called with no args, assume this is part of crazy
197
# lib/canonical/launchpad/webapp/metazcml.py(365)path()
198
# -> return self._compiled_path_expression(self._expression_context)
200
name = '%s(%s)' % (self._name, ', '.join(args))
201
return Whatever(name)
205
def __getattr__(self, name):
206
if name in _BLACKLIST:
209
interface = getattr(self._interface[name], 'schema', None)
212
child = Whatever('%s.%s' % (self._name, name), interface)
214
# Then it's probably displayname, or something.
220
class DefaultCanonicalUrlData(object):
221
implements(ICanonicalUrlData)
224
def __init__(self, name):
225
self.path = '[[%s]]' % (name,)
230
def make_fake_object(interface):
231
return Whatever(interface.__name__, interface)
234
def get_example_canonical_url(interface):
235
fake_obj = make_fake_object(interface)
237
return canonical_url(fake_obj)
238
except (KeyError, TypeError, AssertionError, AttributeError):
239
# If we get any of the errors that we know about, just return the
240
# object. In general, these errors come from app-specific logic that
241
# we can't cheat our way around.
247
gsm = getGlobalSiteManager()
248
gsm.registerAdapter(DefaultCanonicalUrlData)
249
for adapter in iter_page_adapters():
250
print format_page_adapter(adapter)
253
if __name__ == '__main__':