12548.2.4
by Jonathan Lange
Docs. |
1 |
#!/usr/bin/python -S
|
2 |
#
|
|
3 |
# Copyright 2011 Canonical Ltd. This software is licensed under the
|
|
4 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
5 |
||
6 |
"""list-pages -- list the pages that are in Launchpad
|
|
7 |
||
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.
|
|
11 |
||
12548.2.8
by Jonathan Lange
More docs |
12 |
The output contains the page ID, content interface, layer, template and
|
13 |
example URL. Here 'layer' is a clue as to the subdomain.
|
|
14 |
||
15 |
The example URLs will look like::
|
|
16 |
||
17 |
https://launchpad.dev/~<ITeam.name>/+addmember
|
|
18 |
||
19 |
Which means all segments are present, and you can get a real URL by
|
|
20 |
substituting data (in this case, a team name),
|
|
21 |
||
22 |
or::
|
|
23 |
||
24 |
https://launchpad.dev/[[ITranslator]]/+admin
|
|
25 |
||
26 |
Which means we couldn't actually figure out the proper URL. [[ITranslator]]
|
|
27 |
might actually be made of multiple URL segments,
|
|
28 |
||
29 |
or::
|
|
30 |
||
31 |
<IPOFile>/+export
|
|
32 |
||
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.
|
|
35 |
||
12548.2.4
by Jonathan Lange
Docs. |
36 |
"""
|
37 |
||
38 |
# The implementation approach here is fairly simple:
|
|
39 |
# - load the ZCML
|
|
40 |
# - go through all of the adapters, find the ones that look like they might
|
|
41 |
# be for pages.
|
|
42 |
# - bang the adapter nut against the hardest rock we can find until it cracks
|
|
43 |
# - print the nourishing nutty goodness to stdout
|
|
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
44 |
|
45 |
import _pythonpath |
|
14612.2.6
by William Grant
utilities |
46 |
|
47 |
||
12548.2.7
by Jonathan Lange
Flakes |
48 |
_pythonpath
|
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
49 |
|
50 |
from inspect import getmro |
|
51 |
import os |
|
52 |
||
53 |
from zope.app.pagetemplate.simpleviewclass import simple |
|
54 |
from zope.app.testing.functional import FunctionalTestSetup |
|
12548.2.5
by Jonathan Lange
Include an example URL. |
55 |
from zope.component import adapts, getGlobalSiteManager |
56 |
from zope.interface import directlyProvides, implements |
|
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
57 |
from zope.publisher.interfaces.browser import IDefaultBrowserLayer |
58 |
||
14605.1.1
by Curtis Hovey
Moved canonical.config to lp.services. |
59 |
from lp.services.config import config |
14565.2.15
by Curtis Hovey
Moved canonical.launchpad.scripts __init__ to lp.services.scripts. |
60 |
from lp.services.scripts import execute_zcml_for_scripts |
14600.2.2
by Curtis Hovey
Moved webapp to lp.services. |
61 |
from lp.services.webapp.interfaces import ICanonicalUrlData |
62 |
from lp.services.webapp.publisher import canonical_url |
|
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
63 |
|
64 |
||
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
65 |
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) |
66 |
||
67 |
||
13970.7.20
by William Grant
Revert accidental list-pages change. |
68 |
def load_zcml(zopeless=False): |
12548.2.9
by Jonathan Lange
Fix the ZCML loading. |
69 |
"""Load ZCML the way we do in the web site."""
|
70 |
# We can't load ZCML without generating configuration.
|
|
71 |
config.generate_overrides() |
|
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
72 |
if zopeless: |
73 |
execute_zcml_for_scripts() |
|
74 |
else: |
|
12548.2.9
by Jonathan Lange
Fix the ZCML loading. |
75 |
FunctionalTestSetup(os.path.join(ROOT, 'site.zcml')).setUp() |
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
76 |
|
77 |
||
12548.2.2
by Jonathan Lange
Refactor a bit. |
78 |
def is_page_adapter(adapter): |
12548.2.4
by Jonathan Lange
Docs. |
79 |
"""Is 'adapter' a page adapter?
|
80 |
||
81 |
We figure this out by checking to see whether it is adapting from
|
|
82 |
IDefaultBrowserLayer or one of its subclasses.
|
|
83 |
"""
|
|
12548.2.2
by Jonathan Lange
Refactor a bit. |
84 |
for interface in adapter.required: |
85 |
if issubclass(interface, IDefaultBrowserLayer): |
|
86 |
return True |
|
87 |
return False |
|
88 |
||
89 |
||
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
90 |
def get_view(adapter): |
12548.2.4
by Jonathan Lange
Docs. |
91 |
"""Get the view factory associated with 'adapter'."""
|
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
92 |
return adapter.factory |
93 |
||
94 |
||
95 |
def get_template_filename(view): |
|
12548.2.4
by Jonathan Lange
Docs. |
96 |
"""Return the template filename associated with 'view'."""
|
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
97 |
try: |
98 |
filename = view.index.filename |
|
99 |
except AttributeError: |
|
100 |
return None |
|
12548.2.6
by Jonathan Lange
Only show pages that have a html tag. |
101 |
return os.path.abspath(filename) |
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
102 |
|
103 |
||
104 |
def has_page_title(adapter): |
|
12548.2.4
by Jonathan Lange
Docs. |
105 |
"""Does 'adapter' have a page title?
|
106 |
||
107 |
We use this to tell if 'adapter' is indeed an adapter for rendering an
|
|
108 |
entire page. The theory goes that if there's a page title associated with
|
|
109 |
something, then it's a page.
|
|
110 |
"""
|
|
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
111 |
view = get_view(adapter) |
112 |
marker = object() |
|
113 |
template = get_template_filename(view) |
|
114 |
if template is None: |
|
115 |
return False |
|
116 |
name = os.path.splitext(os.path.basename(template))[0].replace('-', '_') |
|
14600.1.11
by Curtis Hovey
Fix until that called obsolete module. |
117 |
if (getattr(view, 'page_title', marker) is marker): |
12548.2.6
by Jonathan Lange
Only show pages that have a html tag. |
118 |
return False |
119 |
return has_html_element(template) |
|
120 |
||
121 |
||
122 |
def has_html_element(template): |
|
123 |
return '</html>' in open(template).read() |
|
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
124 |
|
125 |
||
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
126 |
def iter_page_adapters(): |
127 |
"""Iterate over adapters for browser:page directives."""
|
|
128 |
gsm = getGlobalSiteManager() |
|
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
129 |
return (a for a in gsm.registeredAdapters() |
130 |
if is_page_adapter(a) and has_page_title(a)) |
|
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
131 |
|
132 |
||
12548.2.5
by Jonathan Lange
Include an example URL. |
133 |
def iter_url_adapters(): |
134 |
gsm = getGlobalSiteManager() |
|
135 |
return (a for a in gsm.registeredAdapters() |
|
136 |
if issubclass(a.provided, ICanonicalUrlData)) |
|
137 |
||
138 |
||
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
139 |
def format_page_adapter(a): |
12548.2.4
by Jonathan Lange
Docs. |
140 |
"""Format the adapter 'a' for end-user display.
|
141 |
||
142 |
:return: A string of the form, "pageid,content,layer,template".
|
|
143 |
"""
|
|
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
144 |
factory = a.factory |
145 |
try: |
|
146 |
bases = getmro(factory) |
|
147 |
except AttributeError: |
|
148 |
return a.name |
|
12548.2.4
by Jonathan Lange
Docs. |
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.
|
|
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
155 |
bases = [b for b in bases |
156 |
if b is not simple |
|
157 |
and 'SimpleViewClass' not in b.__name__] |
|
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
158 |
template = get_template_filename(get_view(a)) |
12548.2.6
by Jonathan Lange
Only show pages that have a html tag. |
159 |
if template: |
12548.2.10
by Jonathan Lange
Clarity tweaks from allenap |
160 |
template = os.path.relpath(template, ROOT) |
12548.2.5
by Jonathan Lange
Include an example URL. |
161 |
url = '%s/%s' % (get_example_canonical_url(a.required[0]), a.name) |
162 |
return '%s:%s,%s,%s,%s,%s' % ( |
|
12548.2.3
by Jonathan Lange
Get much closer to showing only the pages. |
163 |
bases[0].__name__, |
164 |
a.name, |
|
165 |
a.required[0].__name__, |
|
166 |
a.required[1].__name__, |
|
12548.2.5
by Jonathan Lange
Include an example URL. |
167 |
template, |
168 |
url, |
|
169 |
)
|
|
170 |
||
171 |
||
172 |
_BLACKLIST = [ |
|
173 |
'__conform__', |
|
174 |
]
|
|
175 |
class Whatever(object): |
|
176 |
||
177 |
def __init__(self, name, interface=None): |
|
178 |
self._name = name |
|
179 |
self._interface = interface |
|
180 |
if interface is not None: |
|
181 |
directlyProvides(self, interface) |
|
182 |
||
183 |
def __str__(self): |
|
12548.2.10
by Jonathan Lange
Clarity tweaks from allenap |
184 |
return '<%s>' % (self._name,) |
12548.2.5
by Jonathan Lange
Include an example URL. |
185 |
|
186 |
def __repr__(self): |
|
187 |
return '<%s>' % str(self) |
|
188 |
||
189 |
def __int__(self): |
|
190 |
return 1 |
|
191 |
||
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
|
|
12548.2.6
by Jonathan Lange
Only show pages that have a html tag. |
196 |
# TALES stuff:
|
14600.2.4
by Curtis Hovey
Fix includes to webapp zcml. |
197 |
# webapp/metazcml.py(365)path()
|
12548.2.6
by Jonathan Lange
Only show pages that have a html tag. |
198 |
# -> return self._compiled_path_expression(self._expression_context)
|
12548.2.5
by Jonathan Lange
Include an example URL. |
199 |
if args: |
200 |
name = '%s(%s)' % (self._name, ', '.join(args)) |
|
201 |
return Whatever(name) |
|
202 |
else: |
|
203 |
return str(self) |
|
204 |
||
205 |
def __getattr__(self, name): |
|
206 |
if name in _BLACKLIST: |
|
207 |
raise AttributeError |
|
208 |
if self._interface: |
|
209 |
interface = getattr(self._interface[name], 'schema', None) |
|
210 |
else: |
|
211 |
interface = None |
|
212 |
child = Whatever('%s.%s' % (self._name, name), interface) |
|
213 |
if 'name' in name: |
|
214 |
# Then it's probably displayname, or something.
|
|
215 |
return str(child) |
|
216 |
else: |
|
217 |
return child |
|
218 |
||
219 |
||
220 |
class DefaultCanonicalUrlData(object): |
|
221 |
implements(ICanonicalUrlData) |
|
222 |
adapts(object) |
|
223 |
||
224 |
def __init__(self, name): |
|
225 |
self.path = '[[%s]]' % (name,) |
|
226 |
self.rootsite = None |
|
227 |
self.inside = None |
|
228 |
||
229 |
||
230 |
def make_fake_object(interface): |
|
231 |
return Whatever(interface.__name__, interface) |
|
232 |
||
233 |
||
234 |
def get_example_canonical_url(interface): |
|
235 |
fake_obj = make_fake_object(interface) |
|
236 |
try: |
|
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.
|
|
242 |
return str(fake_obj) |
|
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
243 |
|
244 |
||
245 |
def main(): |
|
246 |
load_zcml() |
|
247 |
gsm = getGlobalSiteManager() |
|
12548.2.5
by Jonathan Lange
Include an example URL. |
248 |
gsm.registerAdapter(DefaultCanonicalUrlData) |
12548.2.1
by Jonathan Lange
Script to list pages in Lanuchpad |
249 |
for adapter in iter_page_adapters(): |
250 |
print format_page_adapter(adapter) |
|
251 |
||
252 |
||
253 |
if __name__ == '__main__': |
|
254 |
main() |