~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
12548.2.7 by Jonathan Lange
Flakes
46
_pythonpath
12548.2.1 by Jonathan Lange
Script to list pages in Lanuchpad
47
48
from inspect import getmro
49
import os
50
51
from zope.app.pagetemplate.simpleviewclass import simple
52
from zope.app.testing.functional import FunctionalTestSetup
12548.2.5 by Jonathan Lange
Include an example URL.
53
from zope.component import adapts, getGlobalSiteManager
54
from zope.interface import directlyProvides, implements
12548.2.1 by Jonathan Lange
Script to list pages in Lanuchpad
55
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
56
12548.2.9 by Jonathan Lange
Fix the ZCML loading.
57
from canonical.config import config
12548.2.3 by Jonathan Lange
Get much closer to showing only the pages.
58
from canonical.launchpad import pagetitles
12548.2.1 by Jonathan Lange
Script to list pages in Lanuchpad
59
from canonical.launchpad.scripts import execute_zcml_for_scripts
60
from canonical.launchpad.webapp.interfaces import ICanonicalUrlData
12548.2.5 by Jonathan Lange
Include an example URL.
61
from canonical.launchpad.webapp.publisher import canonical_url
12548.2.1 by Jonathan Lange
Script to list pages in Lanuchpad
62
63
12548.2.3 by Jonathan Lange
Get much closer to showing only the pages.
64
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
65
66
12548.2.1 by Jonathan Lange
Script to list pages in Lanuchpad
67
def load_zcml(zopeless=False):
12548.2.9 by Jonathan Lange
Fix the ZCML loading.
68
    """Load ZCML the way we do in the web site."""
69
    # We can't load ZCML without generating configuration.
70
    config.generate_overrides()
12548.2.1 by Jonathan Lange
Script to list pages in Lanuchpad
71
    if zopeless:
72
        execute_zcml_for_scripts()
73
    else:
12548.2.9 by Jonathan Lange
Fix the ZCML loading.
74
        FunctionalTestSetup(os.path.join(ROOT, 'site.zcml')).setUp()
12548.2.1 by Jonathan Lange
Script to list pages in Lanuchpad
75
76
12548.2.2 by Jonathan Lange
Refactor a bit.
77
def is_page_adapter(adapter):
12548.2.4 by Jonathan Lange
Docs.
78
    """Is 'adapter' a page adapter?
79
80
    We figure this out by checking to see whether it is adapting from
81
    IDefaultBrowserLayer or one of its subclasses.
82
    """
12548.2.2 by Jonathan Lange
Refactor a bit.
83
    for interface in adapter.required:
84
        if issubclass(interface, IDefaultBrowserLayer):
85
            return True
86
    return False
87
88
12548.2.3 by Jonathan Lange
Get much closer to showing only the pages.
89
def get_view(adapter):
12548.2.4 by Jonathan Lange
Docs.
90
    """Get the view factory associated with 'adapter'."""
12548.2.3 by Jonathan Lange
Get much closer to showing only the pages.
91
    return adapter.factory
92
93
94
def get_template_filename(view):
12548.2.4 by Jonathan Lange
Docs.
95
    """Return the template filename associated with 'view'."""
12548.2.3 by Jonathan Lange
Get much closer to showing only the pages.
96
    try:
97
        filename = view.index.filename
98
    except AttributeError:
99
        return None
12548.2.6 by Jonathan Lange
Only show pages that have a html tag.
100
    return os.path.abspath(filename)
12548.2.3 by Jonathan Lange
Get much closer to showing only the pages.
101
102
103
def has_page_title(adapter):
12548.2.4 by Jonathan Lange
Docs.
104
    """Does 'adapter' have a page title?
105
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.
109
    """
12548.2.3 by Jonathan Lange
Get much closer to showing only the pages.
110
    view = get_view(adapter)
111
    marker = object()
112
    template = get_template_filename(view)
113
    if template is None:
114
        return False
115
    name = os.path.splitext(os.path.basename(template))[0].replace('-', '_')
12548.2.6 by Jonathan Lange
Only show pages that have a html tag.
116
    if (getattr(view, 'page_title', marker) is marker and
117
        getattr(pagetitles, name, marker) is marker):
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:
197
        #   lib/canonical/launchpad/webapp/metazcml.py(365)path()
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()