~launchpad-pqm/launchpad/devel

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()