1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
#!/usr/bin/python -S
#
# Copyright 2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""list-pages -- list the pages that are in Launchpad
Prints out a CSV of all of the pages that exist in Launchpad. A "page" is a
whole, actual, web page. Something you might actually want to see filling a
browser window. Page fragments, portlets etc. are excluded.
The output contains the page ID, content interface, layer, template and
example URL. Here 'layer' is a clue as to the subdomain.
The example URLs will look like::
https://launchpad.dev/~<ITeam.name>/+addmember
Which means all segments are present, and you can get a real URL by
substituting data (in this case, a team name),
or::
https://launchpad.dev/[[ITranslator]]/+admin
Which means we couldn't actually figure out the proper URL. [[ITranslator]]
might actually be made of multiple URL segments,
or::
<IPOFile>/+export
Which means we have no clue how to figure out the URL. This is generally
because our cheat objects don't match the app-encoded business logic.
"""
# The implementation approach here is fairly simple:
# - load the ZCML
# - go through all of the adapters, find the ones that look like they might
# be for pages.
# - bang the adapter nut against the hardest rock we can find until it cracks
# - print the nourishing nutty goodness to stdout
import _pythonpath
_pythonpath
from inspect import getmro
import os
from zope.app.pagetemplate.simpleviewclass import simple
from zope.app.testing.functional import FunctionalTestSetup
from zope.component import adapts, getGlobalSiteManager
from zope.interface import directlyProvides, implements
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
from canonical.config import config
from lp.services.scripts import execute_zcml_for_scripts
from lp.services.webapp.interfaces import ICanonicalUrlData
from lp.services.webapp.publisher import canonical_url
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
def load_zcml(zopeless=False):
"""Load ZCML the way we do in the web site."""
# We can't load ZCML without generating configuration.
config.generate_overrides()
if zopeless:
execute_zcml_for_scripts()
else:
FunctionalTestSetup(os.path.join(ROOT, 'site.zcml')).setUp()
def is_page_adapter(adapter):
"""Is 'adapter' a page adapter?
We figure this out by checking to see whether it is adapting from
IDefaultBrowserLayer or one of its subclasses.
"""
for interface in adapter.required:
if issubclass(interface, IDefaultBrowserLayer):
return True
return False
def get_view(adapter):
"""Get the view factory associated with 'adapter'."""
return adapter.factory
def get_template_filename(view):
"""Return the template filename associated with 'view'."""
try:
filename = view.index.filename
except AttributeError:
return None
return os.path.abspath(filename)
def has_page_title(adapter):
"""Does 'adapter' have a page title?
We use this to tell if 'adapter' is indeed an adapter for rendering an
entire page. The theory goes that if there's a page title associated with
something, then it's a page.
"""
view = get_view(adapter)
marker = object()
template = get_template_filename(view)
if template is None:
return False
name = os.path.splitext(os.path.basename(template))[0].replace('-', '_')
if (getattr(view, 'page_title', marker) is marker):
return False
return has_html_element(template)
def has_html_element(template):
return '</html>' in open(template).read()
def iter_page_adapters():
"""Iterate over adapters for browser:page directives."""
gsm = getGlobalSiteManager()
return (a for a in gsm.registeredAdapters()
if is_page_adapter(a) and has_page_title(a))
def iter_url_adapters():
gsm = getGlobalSiteManager()
return (a for a in gsm.registeredAdapters()
if issubclass(a.provided, ICanonicalUrlData))
def format_page_adapter(a):
"""Format the adapter 'a' for end-user display.
:return: A string of the form, "pageid,content,layer,template".
"""
factory = a.factory
try:
bases = getmro(factory)
except AttributeError:
return a.name
# Zope, bless it, does all sorts of type shenanigans to obscure the name
# of the factory as it appears in our code. (See
# z.browserpage.metaconfigure.page and
# z.app.pagetemplate.simpleviewclass). Ideally, we'd just construct the
# view, but that's hard, since in general they won't construct without an
# object that implements the interface they need.
bases = [b for b in bases
if b is not simple
and 'SimpleViewClass' not in b.__name__]
template = get_template_filename(get_view(a))
if template:
template = os.path.relpath(template, ROOT)
url = '%s/%s' % (get_example_canonical_url(a.required[0]), a.name)
return '%s:%s,%s,%s,%s,%s' % (
bases[0].__name__,
a.name,
a.required[0].__name__,
a.required[1].__name__,
template,
url,
)
_BLACKLIST = [
'__conform__',
]
class Whatever(object):
def __init__(self, name, interface=None):
self._name = name
self._interface = interface
if interface is not None:
directlyProvides(self, interface)
def __str__(self):
return '<%s>' % (self._name,)
def __repr__(self):
return '<%s>' % str(self)
def __int__(self):
return 1
def __call__(self, *args, **kwargs):
args = map(repr, args)
args.extend('%s=%r' % (k, v) for k, v in kwargs.iteritems())
# If we're being called with no args, assume this is part of crazy
# TALES stuff:
# lib/canonical/launchpad/webapp/metazcml.py(365)path()
# -> return self._compiled_path_expression(self._expression_context)
if args:
name = '%s(%s)' % (self._name, ', '.join(args))
return Whatever(name)
else:
return str(self)
def __getattr__(self, name):
if name in _BLACKLIST:
raise AttributeError
if self._interface:
interface = getattr(self._interface[name], 'schema', None)
else:
interface = None
child = Whatever('%s.%s' % (self._name, name), interface)
if 'name' in name:
# Then it's probably displayname, or something.
return str(child)
else:
return child
class DefaultCanonicalUrlData(object):
implements(ICanonicalUrlData)
adapts(object)
def __init__(self, name):
self.path = '[[%s]]' % (name,)
self.rootsite = None
self.inside = None
def make_fake_object(interface):
return Whatever(interface.__name__, interface)
def get_example_canonical_url(interface):
fake_obj = make_fake_object(interface)
try:
return canonical_url(fake_obj)
except (KeyError, TypeError, AssertionError, AttributeError):
# If we get any of the errors that we know about, just return the
# object. In general, these errors come from app-specific logic that
# we can't cheat our way around.
return str(fake_obj)
def main():
load_zcml()
gsm = getGlobalSiteManager()
gsm.registerAdapter(DefaultCanonicalUrlData)
for adapter in iter_page_adapters():
print format_page_adapter(adapter)
if __name__ == '__main__':
main()
|