devctl.py 24.5 KB
Newer Older
Adrien Di Mascio's avatar
Adrien Di Mascio committed
1
2
3
4
"""additional cubicweb-ctl commands and command handlers for cubicweb and cubicweb's
cubes development

:organization: Logilab
5
:copyright: 2001-2010 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
Adrien Di Mascio's avatar
Adrien Di Mascio committed
6
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
7
:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
Adrien Di Mascio's avatar
Adrien Di Mascio committed
8
9
10
11
"""
__docformat__ = "restructuredtext en"

import sys
12
from datetime import datetime
13
from os import mkdir, chdir, getcwd
Adrien Di Mascio's avatar
Adrien Di Mascio committed
14
from os.path import join, exists, abspath, basename, normpath, split, isdir
15
from copy import deepcopy
16
from warnings import warn
Adrien Di Mascio's avatar
Adrien Di Mascio committed
17
18
19

from logilab.common import STD_BLACKLIST
from logilab.common.modutils import get_module_files
20
from logilab.common.textutils import splitstrip
21
from logilab.common.shellutils import ASK
22
from logilab.common.clcommands import register_commands
Adrien Di Mascio's avatar
Adrien Di Mascio committed
23
24

from cubicweb.__pkginfo__ import version as cubicwebversion
25
from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage
26
from cubicweb.toolsutils import Command, copy_skeleton, underline_title
27
from cubicweb.schema import CONSTRAINTS
Adrien Di Mascio's avatar
Adrien Di Mascio committed
28
29
30
from cubicweb.web.webconfig import WebConfiguration
from cubicweb.server.serverconfig import ServerConfiguration

31

Sylvain Thenault's avatar
Sylvain Thenault committed
32
class DevCubeConfiguration(ServerConfiguration, WebConfiguration):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
33
34
    """dummy config to get full library schema and entities"""
    creating = True
35
36
    cubicweb_appobject_path = ServerConfiguration.cubicweb_appobject_path | WebConfiguration.cubicweb_appobject_path
    cube_appobject_path = ServerConfiguration.cube_appobject_path | WebConfiguration.cube_appobject_path
Sylvain Thenault's avatar
Sylvain Thenault committed
37
38
39
40

    def __init__(self, cube):
        super(DevCubeConfiguration, self).__init__(cube)
        if cube is None:
Adrien Di Mascio's avatar
Adrien Di Mascio committed
41
42
            self._cubes = ()
        else:
43
            self._cubes = self.reorder_cubes(self.expand_cubes(self.my_cubes(cube)))
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
44
45
46

    def my_cubes(self, cube):
        return (cube,) + self.cube_dependencies(cube) + self.cube_recommends(cube)
47

Adrien Di Mascio's avatar
Adrien Di Mascio committed
48
49
    @property
    def apphome(self):
Sylvain Thenault's avatar
Sylvain Thenault committed
50
51
52
        return None
    def main_config_file(self):
        return None
Adrien Di Mascio's avatar
Adrien Di Mascio committed
53
54
55
56
57
58
    def init_log(self, debug=None):
        pass
    def load_configuration(self):
        pass


Sylvain Thenault's avatar
Sylvain Thenault committed
59
60
61
62
class DevDepConfiguration(DevCubeConfiguration):
    """configuration to use to generate cubicweb po files or to use as "library" configuration
    to filter out message ids from cubicweb and dependencies of a cube
    """
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
63
64
65

    def my_cubes(self, cube):
        return self.cube_dependencies(cube) + self.cube_recommends(cube)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
66

Sylvain Thenault's avatar
Sylvain Thenault committed
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
    def default_log_file(self):
        return None


def cleanup_sys_modules(config):
    # cleanup sys.modules, required when we're updating multiple cubes
    for name, mod in sys.modules.items():
        if mod is None:
            # duh ? logilab.common.os for instance
            del sys.modules[name]
            continue
        if not hasattr(mod, '__file__'):
            continue
        for path in config.vregistry_path():
            if mod.__file__.startswith(path):
                del sys.modules[name]
                break
Sylvain Thénault's avatar
Sylvain Thénault committed
84
85
86
87
88
    # fresh rtags
    from cubicweb import rtags
    from cubicweb.web import uicfg
    rtags.RTAGS[:] = []
    reload(uicfg)
89

Adrien Di Mascio's avatar
Adrien Di Mascio committed
90
91
92
93
94
95
def generate_schema_pot(w, cubedir=None):
    """generate a pot file with schema specific i18n messages

    notice that relation definitions description and static vocabulary
    should be marked using '_' and extracted using xgettext
    """
96
    from cubicweb.cwvreg import CubicWebVRegistry
Adrien Di Mascio's avatar
Adrien Di Mascio committed
97
    cube = cubedir and split(cubedir)[-1]
98
99
100
    libconfig = DevDepConfiguration(cube)
    libconfig.cleanup_interface_sobjects = False
    cleanup_sys_modules(libconfig)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
101
    if cubedir:
Sylvain Thenault's avatar
Sylvain Thenault committed
102
        config = DevCubeConfiguration(cube)
103
        config.cleanup_interface_sobjects = False
Adrien Di Mascio's avatar
Adrien Di Mascio committed
104
    else:
105
106
        config = libconfig
        libconfig = None
107
    schema = config.load_schema(remove_unused_rtypes=False)
108
    vreg = CubicWebVRegistry(config)
Sylvain Thenault's avatar
Sylvain Thenault committed
109
    # set_schema triggers objects registrations
Adrien Di Mascio's avatar
Adrien Di Mascio committed
110
111
    vreg.set_schema(schema)
    w(DEFAULT_POT_HEAD)
112
    _generate_schema_pot(w, vreg, schema, libconfig=libconfig, cube=cube)
113
114


115
def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
116
    from cubicweb.i18n import add_msg
117
    from cubicweb.web import uicfg
118
119
    from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES
    no_context_rtypes = META_RTYPES | SYSTEM_RTYPES
120
    w('# schema pot file, generated on %s\n' % datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
121
122
123
    w('# \n')
    w('# singular and plural forms for each entity type\n')
    w('\n')
124
    vregdone = set()
125
    if libconfig is not None:
126
        from cubicweb.cwvreg import CubicWebVRegistry, clear_rtag_objects
127
        libschema = libconfig.load_schema(remove_unused_rtypes=False)
128
        afs = deepcopy(uicfg.autoform_section)
129
130
131
132
133
        appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu)
        clear_rtag_objects()
        cleanup_sys_modules(libconfig)
        libvreg = CubicWebVRegistry(libconfig)
        libvreg.set_schema(libschema) # trigger objects registration
134
        libafs = uicfg.autoform_section
135
136
137
        libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu
        # prefill vregdone set
        list(_iter_vreg_objids(libvreg, vregdone))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
138
    else:
139
        libschema = {}
140
        afs = uicfg.autoform_section
141
        appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
142
143
        for cstrtype in CONSTRAINTS:
            add_msg(w, cstrtype)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
144
    done = set()
145
    for eschema in sorted(schema.entities()):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
146
        etype = eschema.type
147
148
149
        if etype not in libschema:
            add_msg(w, etype)
            add_msg(w, '%s_plural' % etype)
150
            if not eschema.final:
151
152
153
154
155
                add_msg(w, 'This %s' % etype)
                add_msg(w, 'New %s' % etype)
            if eschema.description and not eschema.description in done:
                done.add(eschema.description)
                add_msg(w, eschema.description)
156
        if eschema.final:
157
158
            continue
        for rschema, targetschemas, role in eschema.relation_definitions(True):
159
160
            if rschema.final:
                continue
161
            for tschema in targetschemas:
162
                fsections = afs.etype_get(eschema, rschema, role, tschema)
163
                if 'main_inlined' in fsections and \
164
                       (libconfig is None or not
165
                        'main_inlined' in libafs.etype_get(
166
                            eschema, rschema, role, tschema)):
167
168
                    add_msg(w, 'add a %s' % tschema,
                            'inlined:%s.%s.%s' % (etype, rschema, role))
169
                    add_msg(w, str(tschema),
170
                            'inlined:%s.%s.%s' % (etype, rschema, role))
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
                if appearsin_addmenu.etype_get(eschema, rschema, role, tschema) and \
                       (libconfig is None or not
                        libappearsin_addmenu.etype_get(eschema, rschema, role, tschema)):
                    if role == 'subject':
                        label = 'add %s %s %s %s' % (eschema, rschema,
                                                     tschema, role)
                        label2 = "creating %s (%s %%(linkto)s %s %s)" % (
                            tschema, eschema, rschema, tschema)
                    else:
                        label = 'add %s %s %s %s' % (tschema, rschema,
                                                     eschema, role)
                        label2 = "creating %s (%s %s %s %%(linkto)s)" % (
                            tschema, tschema, rschema, eschema)
                    add_msg(w, label)
                    add_msg(w, label2)
Sylvain Thénault's avatar
Sylvain Thénault committed
186
187
            # XXX also generate "creating ...' messages for actions in the
            # addrelated submenu
Adrien Di Mascio's avatar
Adrien Di Mascio committed
188
    w('# subject and object forms for each relation type\n')
189
    w('# (no object form for final or symetric relation types)\n')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
190
    w('\n')
191
    for rschema in sorted(schema.relations()):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
192
        rtype = rschema.type
193
194
195
196
197
198
199
200
201
202
        if rtype not in libschema:
            # bw compat, necessary until all translation of relation are done properly...
            add_msg(w, rtype)
            if rschema.description and rschema.description not in done:
                done.add(rschema.description)
                add_msg(w, rschema.description)
            done.add(rtype)
            librschema = None
        else:
            librschema = libschema.rschema(rtype)
203
        # add context information only for non-metadata rtypes
204
        if rschema not in no_context_rtypes:
205
            libsubjects = librschema and librschema.subjects() or ()
206
            for subjschema in rschema.subjects():
207
208
                if not subjschema in libsubjects:
                    add_msg(w, rtype, subjschema.type)
209
        if not (schema.rschema(rtype).final or rschema.symetric):
210
            if rschema not in no_context_rtypes:
211
212
213
214
215
216
217
218
                libobjects = librschema and librschema.objects() or ()
                for objschema in rschema.objects():
                    if not objschema in libobjects:
                        add_msg(w, '%s_object' % rtype, objschema.type)
            if rtype not in libschema:
                # bw compat, necessary until all translation of relation are done properly...
                add_msg(w, '%s_object' % rtype)
    for objid in _iter_vreg_objids(vreg, vregdone):
219
220
221
        add_msg(w, '%s_description' % objid)
        add_msg(w, objid)

Sylvain Thénault's avatar
Sylvain Thénault committed
222

223
def _iter_vreg_objids(vreg, done, prefix=None):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
224
225
226
    for reg, objdict in vreg.items():
        for objects in objdict.values():
            for obj in objects:
Sylvain Thénault's avatar
Sylvain Thénault committed
227
                objid = '%s_%s' % (reg, obj.__regid__)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
228
                if objid in done:
229
                    break
Sylvain Thénault's avatar
Sylvain Thénault committed
230
231
232
233
234
                try: # XXX < 3.6 bw compat
                    pdefs = obj.property_defs
                except AttributeError:
                    pdefs = getattr(obj, 'cw_property_defs', {})
                if pdefs:
235
                    yield objid
Adrien Di Mascio's avatar
Adrien Di Mascio committed
236
                    done.add(objid)
237
                    break
238

239

240
def defined_in_library(etype, rtype, tetype, role):
241
242
    """return true if the given relation definition exists in cubicweb's library
    """
Adrien Di Mascio's avatar
Adrien Di Mascio committed
243
244
    if libschema is None:
        return False
245
    if role == 'subject':
Adrien Di Mascio's avatar
Adrien Di Mascio committed
246
247
248
249
250
251
252
253
254
        subjtype, objtype = etype, tetype
    else:
        subjtype, objtype = tetype, etype
    try:
        return libschema.rschema(rtype).has_rdef(subjtype, objtype)
    except KeyError:
        return False


255
LANGS = ('en', 'fr', 'es')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
I18NDIR = join(BASEDIR, 'i18n')
DEFAULT_POT_HEAD = r'''msgid ""
msgstr ""
"Project-Id-Version: cubicweb %s\n"
"PO-Revision-Date: 2008-03-28 18:14+0100\n"
"Last-Translator: Logilab Team <contact@logilab.fr>\n"
"Language-Team: fr <contact@logilab.fr>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: cubicweb-devtools\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

''' % cubicwebversion


class UpdateCubicWebCatalogCommand(Command):
    """Update i18n catalogs for cubicweb library.
274

Adrien Di Mascio's avatar
Adrien Di Mascio committed
275
276
277
    It will regenerate cubicweb/i18n/xx.po files. You'll have then to edit those
    files to add translations of newly added messages.
    """
278
    name = 'i18ncubicweb'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
279
280
281
282
283
284

    def run(self, args):
        """run the command with its specific arguments"""
        if args:
            raise BadCommandUsage('Too much arguments')
        import shutil
285
        import tempfile
Adrien Di Mascio's avatar
Adrien Di Mascio committed
286
287
        import yams
        from logilab.common.fileutils import ensure_fs_mode
288
        from logilab.common.shellutils import globfind, find, rm
289
        from cubicweb.i18n import extract_from_tal, execute
290
        tempdir = tempfile.mkdtemp()
291
        potfiles = [join(I18NDIR, 'static-messages.pot')]
292
        print '-> extract schema messages.'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
293
294
295
296
297
298
299
        schemapot = join(tempdir, 'schema.pot')
        potfiles.append(schemapot)
        # explicit close necessary else the file may not be yet flushed when
        # we'll using it below
        schemapotstream = file(schemapot, 'w')
        generate_schema_pot(schemapotstream.write, cubedir=None)
        schemapotstream.close()
300
        print '-> extract TAL messages.'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
301
302
        tali18nfile = join(tempdir, 'tali18n.py')
        extract_from_tal(find(join(BASEDIR, 'web'), ('.py', '.pt')), tali18nfile)
303
        print '-> generate .pot files.'
304
        for id, files, lang in [('pycubicweb', get_module_files(BASEDIR) + list(globfind(join(BASEDIR, 'misc', 'migration'), '*.py')), None),
305
                                ('schemadescr', globfind(join(BASEDIR, 'schemas'), '*.py'), None),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
306
307
                                ('yams', get_module_files(yams.__path__[0]), None),
                                ('tal', [tali18nfile], None),
308
                                ('js', globfind(join(BASEDIR, 'web'), 'cub*.js'), 'java'),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
309
                                ]:
310
            cmd = 'xgettext --no-location --omit-header -k_ -o %s %s'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
311
312
            if lang is not None:
                cmd += ' -L %s' % lang
313
            potfile = join(tempdir, '%s.pot' % id)
314
            execute(cmd % (potfile, ' '.join('"%s"' % f for f in files)))
315
316
317
            if exists(potfile):
                potfiles.append(potfile)
            else:
318
319
                print '-> WARNING: %s file was not generated' % potfile
        print '-> merging %i .pot files' % len(potfiles)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
320
        cubicwebpot = join(tempdir, 'cubicweb.pot')
321
        execute('msgcat -o %s %s' % (cubicwebpot, ' '.join('"%s"' % f for f in potfiles)))
322
        print '-> merging main pot file with existing translations.'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
323
324
325
326
        chdir(I18NDIR)
        toedit = []
        for lang in LANGS:
            target = '%s.po' % lang
327
            execute('msgmerge -N --sort-output -o "%snew" "%s" "%s"' % (target, target, cubicwebpot))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
328
329
330
331
            ensure_fs_mode(target)
            shutil.move('%snew' % target, target)
            toedit.append(abspath(target))
        # cleanup
332
        rm(tempdir)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
333
        # instructions pour la suite
334
335
        print '-> regenerated CubicWeb\'s .po catalogs.'
        print '\nYou can now edit the following files:'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
336
        print '* ' + '\n* '.join(toedit)
337
        print 'when you are done, run "cubicweb-ctl i18ncube yourcube".'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
338
339
340
341
342
343


class UpdateTemplateCatalogCommand(Command):
    """Update i18n catalogs for cubes. If no cube is specified, update
    catalogs of all registered cubes.
    """
344
    name = 'i18ncube'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
345
    arguments = '[<cube>...]'
346

Adrien Di Mascio's avatar
Adrien Di Mascio committed
347
348
349
    def run(self, args):
        """run the command with its specific arguments"""
        if args:
350
            cubes = [DevCubeConfiguration.cube_dir(cube) for cube in args]
Adrien Di Mascio's avatar
Adrien Di Mascio committed
351
        else:
352
353
            cubes = [DevCubeConfiguration.cube_dir(cube) for cube in DevCubeConfiguration.available_cubes()]
            cubes = [cubepath for cubepath in cubes if exists(join(cubepath, 'i18n'))]
Adrien Di Mascio's avatar
Adrien Di Mascio committed
354
355
        update_cubes_catalogs(cubes)

356

Adrien Di Mascio's avatar
Adrien Di Mascio committed
357
358
def update_cubes_catalogs(cubes):
    for cubedir in cubes:
Sylvain Thénault's avatar
Sylvain Thénault committed
359
        toedit = []
Adrien Di Mascio's avatar
Adrien Di Mascio committed
360
        if not isdir(cubedir):
361
            print '-> ignoring %s that is not a directory.' % cubedir
Adrien Di Mascio's avatar
Adrien Di Mascio committed
362
            continue
363
364
365
366
367
        try:
            toedit += update_cube_catalogs(cubedir)
        except Exception:
            import traceback
            traceback.print_exc()
Sylvain Thénault's avatar
Sylvain Thénault committed
368
369
370
371
372
373
374
375
            print '-> error while updating catalogs for cube', cubedir
        else:
            # instructions pour la suite
            print '-> regenerated .po catalogs for cube %s.' % cubedir
            print '\nYou can now edit the following files:'
            print '* ' + '\n* '.join(toedit)
            print ('When you are done, run "cubicweb-ctl i18ninstance '
                   '<yourinstance>" to see changes in your instances.')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
376

377
378
def update_cube_catalogs(cubedir):
    import shutil
379
    import tempfile
380
381
    from logilab.common.fileutils import ensure_fs_mode
    from logilab.common.shellutils import find, rm
382
    from cubicweb.i18n import extract_from_tal, execute
383
384
    toedit = []
    cube = basename(normpath(cubedir))
385
    tempdir = tempfile.mkdtemp()
386
    print underline_title('Updating i18n catalogs for cube %s' % cube)
387
    chdir(cubedir)
388
389
390
391
392
393
394
395
    if exists(join('i18n', 'entities.pot')):
        warn('entities.pot is deprecated, rename file to static-messages.pot (%s)'
             % join('i18n', 'entities.pot'), DeprecationWarning)
        potfiles = [join('i18n', 'entities.pot')]
    elif exists(join('i18n', 'static-messages.pot')):
        potfiles = [join('i18n', 'static-messages.pot')]
    else:
        potfiles = []
396
    print '-> extract schema messages'
397
398
399
400
401
402
403
    schemapot = join(tempdir, 'schema.pot')
    potfiles.append(schemapot)
    # explicit close necessary else the file may not be yet flushed when
    # we'll using it below
    schemapotstream = file(schemapot, 'w')
    generate_schema_pot(schemapotstream.write, cubedir)
    schemapotstream.close()
404
    print '-> extract TAL messages'
405
406
    tali18nfile = join(tempdir, 'tali18n.py')
    extract_from_tal(find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',)), tali18nfile)
407
    print '-> extract Javascript messages'
408
409
410
411
412
413
414
415
    jsfiles =  [jsfile for jsfile in find('.', '.js') if basename(jsfile).startswith('cub')]
    if jsfiles:
        tmppotfile = join(tempdir, 'js.pot')
        execute('xgettext --no-location --omit-header -k_ -L java --from-code=utf-8 -o %s %s'
                % (tmppotfile, ' '.join(jsfiles)))
        # no pot file created if there are no string to translate
        if exists(tmppotfile):
            potfiles.append(tmppotfile)
416
    print '-> create cube-specific catalog'
417
418
419
420
    tmppotfile = join(tempdir, 'generated.pot')
    cubefiles = find('.', '.py', blacklist=STD_BLACKLIST+('test',))
    cubefiles.append(tali18nfile)
    execute('xgettext --no-location --omit-header -k_ -o %s %s'
421
            % (tmppotfile, ' '.join('"%s"' % f for f in cubefiles)))
422
423
424
    if exists(tmppotfile): # doesn't exists of no translation string found
        potfiles.append(tmppotfile)
    potfile = join(tempdir, 'cube.pot')
425
    print '-> merging %i .pot files:' % len(potfiles)
426
427
    execute('msgcat -o %s %s' % (potfile,
                                 ' '.join('"%s"' % f for f in potfiles)))
428
    print '-> merging main pot file with existing translations:'
429
430
    chdir('i18n')
    for lang in LANGS:
431
        print '-> language', lang
432
433
434
435
        cubepo = '%s.po' % lang
        if not exists(cubepo):
            shutil.copy(potfile, cubepo)
        else:
436
            execute('msgmerge -N -s -o %snew %s %s' % (cubepo, cubepo, potfile))
437
438
439
440
441
442
443
444
            ensure_fs_mode(cubepo)
            shutil.move('%snew' % cubepo, cubepo)
        toedit.append(abspath(cubepo))
    # cleanup
    rm(tempdir)
    return toedit


Adrien Di Mascio's avatar
Adrien Di Mascio committed
445
446
447
448
449
450
class LiveServerCommand(Command):
    """Run a server from within a cube directory.
    """
    name = 'live-server'
    arguments = ''
    options = ()
451

Adrien Di Mascio's avatar
Adrien Di Mascio committed
452
453
454
455
456
457
    def run(self, args):
        """run the command with its specific arguments"""
        from cubicweb.devtools.livetest import runserver
        runserver()


458
class NewCubeCommand(Command):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
459
460
461
462
463
464
465
466
    """Create a new cube.

    <cubename>
      the name of the new cube
    """
    name = 'newcube'
    arguments = '<cubename>'

467
    options = (
468
469
470
471
472
        ("directory",
         {'short': 'd', 'type' : 'string', 'metavar': '<cubes directory>',
          'help': 'directory where the new cube should be created',
          }
         ),
473
474
475
476
477
478
        ("verbose",
         {'short': 'v', 'type' : 'yn', 'metavar': '<verbose>',
          'default': 'n',
          'help': 'verbose mode: will ask all possible configuration questions',
          }
         ),
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
        ("author",
         {'short': 'a', 'type' : 'string', 'metavar': '<author>',
          'default': 'LOGILAB S.A. (Paris, FRANCE)',
          'help': 'cube author',
          }
         ),
        ("author-email",
         {'short': 'e', 'type' : 'string', 'metavar': '<email>',
          'default': 'contact@logilab.fr',
          'help': 'cube author\'s email',
          }
         ),
        ("author-web-site",
         {'short': 'w', 'type' : 'string', 'metavar': '<web site>',
          'default': 'http://www.logilab.fr',
          'help': 'cube author\'s web site',
          }
         ),
497
498
        )

499

Adrien Di Mascio's avatar
Adrien Di Mascio committed
500
501
502
503
    def run(self, args):
        if len(args) != 1:
            raise BadCommandUsage("exactly one argument (cube name) is expected")
        cubename, = args
504
        verbose = self.get('verbose')
505
        cubesdir = self.get('directory')
506
507
        if not cubesdir:
            cubespath = ServerConfiguration.cubes_search_path()
508
509
510
511
512
            if len(cubespath) > 1:
                raise BadCommandUsage("can't guess directory where to put the new cube."
                                      " Please specify it using the --directory option")
            cubesdir = cubespath[0]
        if not isdir(cubesdir):
513
            print "-> creating cubes directory", cubesdir
Adrien Di Mascio's avatar
Adrien Di Mascio committed
514
            try:
515
                mkdir(cubesdir)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
516
            except OSError, err:
517
                self.fail("failed to create directory %r\n(%s)" % (cubesdir, err))
518
        cubedir = join(cubesdir, cubename)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
519
520
521
        if exists(cubedir):
            self.fail("%s already exists !" % (cubedir))
        skeldir = join(BASEDIR, 'skeleton')
522
        default_name = 'cubicweb-%s' % cubename.lower()
523
        if verbose:
524
            distname = raw_input('Debian name for your cube ? [%s]): ' % default_name).strip()
525
            if not distname:
526
                distname = default_name
527
            elif not distname.startswith('cubicweb-'):
528
                if ASK.confirm('Do you mean cubicweb-%s ?' % distname):
529
530
                    distname = 'cubicweb-' + distname
        else:
531
            distname = default_name
532

533
534
        longdesc = shortdesc = raw_input('Enter a short description for your cube: ')
        if verbose:
535
            longdesc = raw_input('Enter a long description (leave empty to reuse the short one): ')
536
537
538
539
540
541
        if verbose:
            includes = self._ask_for_dependancies()
            if len(includes) == 1:
                dependancies = '%r,' % includes[0]
            else:
                dependancies = ', '.join(repr(cube) for cube in includes)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
542
        else:
543
            dependancies = ''
Adrien Di Mascio's avatar
Adrien Di Mascio committed
544
545
546
547
548
549
        context = {'cubename' : cubename,
                   'distname' : distname,
                   'shortdesc' : shortdesc,
                   'longdesc' : longdesc or shortdesc,
                   'dependancies' : dependancies,
                   'version'  : cubicwebversion,
550
                   'year'  : str(datetime.now().year),
551
552
553
                   'author': self['author'],
                   'author-email': self['author-email'],
                   'author-web-site': self['author-web-site'],
Adrien Di Mascio's avatar
Adrien Di Mascio committed
554
555
556
557
558
559
                   }
        copy_skeleton(skeldir, cubedir, context)

    def _ask_for_dependancies(self):
        includes = []
        for stdtype in ServerConfiguration.available_cubes():
560
561
562
            answer = ASK.ask("Depends on cube %s? " % stdtype,
                             ('N','y','skip','type'), 'N')
            if answer == 'y':
Adrien Di Mascio's avatar
Adrien Di Mascio committed
563
                includes.append(stdtype)
564
            if answer == 'type':
565
                includes = splitstrip(raw_input('type dependancies: '))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
566
                break
567
            elif answer == 'skip':
Adrien Di Mascio's avatar
Adrien Di Mascio committed
568
569
                break
        return includes
570

571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588

class ExamineLogCommand(Command):
    """Examine a rql log file.

    usage: python exlog.py < rql.log

    will print out the following table

      total execution time || number of occurences || rql query

    sorted by descending total execution time

    chances are the lines at the top are the ones that will bring
    the higher benefit after optimisation. Start there.
    """
    name = 'exlog'
    options = (
        )
589

590
591
592
593
594
    def run(self, args):
        if args:
            raise BadCommandUsage("no argument expected")
        import re
        requests = {}
595
        for lineno, line in enumerate(sys.stdin):
596
597
598
            if not ' WHERE ' in line:
                continue
            #sys.stderr.write( line )
599
600
601
            try:
                rql, time = line.split('--')
                rql = re.sub("(\'\w+': \d*)", '', rql)
602
603
                if '{' in rql:
                    rql = rql[:rql.index('{')]
604
605
606
                req = requests.setdefault(rql, [])
                time.strip()
                chunks = time.split()
607
                clocktime = float(chunks[0][1:])
608
                cputime = float(chunks[-3])
609
                req.append( (clocktime, cputime) )
610
            except Exception, exc:
611
                sys.stderr.write('Line %s: %s (%s)\n' % (lineno, exc, line))
612
613
614

        stat = []
        for rql, times in requests.items():
615
616
617
            stat.append( (sum(time[0] for time in times),
                          sum(time[1] for time in times),
                          len(times), rql) )
618
619
620

        stat.sort()
        stat.reverse()
621

622
623
624
625
        total_time = sum(clocktime for clocktime, cputime, occ, rql in stat)*0.01
        print 'Percentage;Cumulative Time (clock);Cumulative Time (CPU);Occurences;Query'
        for clocktime, cputime, occ, rql in stat:
            print '%.2f;%.2f;%.2f;%s;%s' % (clocktime/total_time, clocktime, cputime, occ, rql)
626

Adrien Di Mascio's avatar
Adrien Di Mascio committed
627
628
629
register_commands((UpdateCubicWebCatalogCommand,
                   UpdateTemplateCatalogCommand,
                   LiveServerCommand,
630
                   NewCubeCommand,
631
                   ExamineLogCommand,
Adrien Di Mascio's avatar
Adrien Di Mascio committed
632
                   ))