devctl.py 24.2 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-2009 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
25
from cubicweb import (CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage,
                      underline_title)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
26
from cubicweb.__pkginfo__ import version as cubicwebversion
27
from cubicweb.toolsutils import Command, copy_skeleton
Adrien Di Mascio's avatar
Adrien Di Mascio committed
28
29
30
from cubicweb.web.webconfig import WebConfiguration
from cubicweb.server.serverconfig import ServerConfiguration

Sylvain Thenault's avatar
Sylvain Thenault committed
31
class DevCubeConfiguration(ServerConfiguration, WebConfiguration):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
32
33
    """dummy config to get full library schema and entities"""
    creating = True
34
35
    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
36
37
38
39

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

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

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


Sylvain Thenault's avatar
Sylvain Thenault committed
58
59
60
61
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
62
63
64

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

Sylvain Thenault's avatar
Sylvain Thenault committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
    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
83
84
85
86
87
    # fresh rtags
    from cubicweb import rtags
    from cubicweb.web import uicfg
    rtags.RTAGS[:] = []
    reload(uicfg)
88

Adrien Di Mascio's avatar
Adrien Di Mascio committed
89
90
91
92
93
94
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
    """
95
    from cubicweb.cwvreg import CubicWebVRegistry
Adrien Di Mascio's avatar
Adrien Di Mascio committed
96
    cube = cubedir and split(cubedir)[-1]
97
98
99
    libconfig = DevDepConfiguration(cube)
    libconfig.cleanup_interface_sobjects = False
    cleanup_sys_modules(libconfig)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
100
    if cubedir:
Sylvain Thenault's avatar
Sylvain Thenault committed
101
        config = DevCubeConfiguration(cube)
102
        config.cleanup_interface_sobjects = False
Adrien Di Mascio's avatar
Adrien Di Mascio committed
103
    else:
104
105
        config = libconfig
        libconfig = None
106
    schema = config.load_schema(remove_unused_rtypes=False)
107
    vreg = CubicWebVRegistry(config)
Sylvain Thenault's avatar
Sylvain Thenault committed
108
    # set_schema triggers objects registrations
Adrien Di Mascio's avatar
Adrien Di Mascio committed
109
110
    vreg.set_schema(schema)
    w(DEFAULT_POT_HEAD)
111
    _generate_schema_pot(w, vreg, schema, libconfig=libconfig, cube=cube)
112
113


114
def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
115
    from cubicweb.common.i18n import add_msg
116
    from cubicweb.web import uicfg
117
118
    from cubicweb.schema import META_RTYPES, SYSTEM_RTYPES
    no_context_rtypes = META_RTYPES | SYSTEM_RTYPES
119
    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
120
121
122
    w('# \n')
    w('# singular and plural forms for each entity type\n')
    w('\n')
123
    vregdone = set()
124
    if libconfig is not None:
125
        from cubicweb.cwvreg import CubicWebVRegistry, clear_rtag_objects
126
        libschema = libconfig.load_schema(remove_unused_rtypes=False)
127
128
129
130
131
132
133
134
135
136
        rinlined = deepcopy(uicfg.autoform_is_inlined)
        appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu)
        clear_rtag_objects()
        cleanup_sys_modules(libconfig)
        libvreg = CubicWebVRegistry(libconfig)
        libvreg.set_schema(libschema) # trigger objects registration
        librinlined = uicfg.autoform_is_inlined
        libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu
        # prefill vregdone set
        list(_iter_vreg_objids(libvreg, vregdone))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
137
    else:
138
139
140
        libschema = {}
        rinlined = uicfg.autoform_is_inlined
        appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
Adrien Di Mascio's avatar
Adrien Di Mascio committed
141
    done = set()
142
    for eschema in sorted(schema.entities()):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
143
        etype = eschema.type
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
        if etype not in libschema:
            add_msg(w, etype)
            add_msg(w, '%s_plural' % etype)
            if not eschema.is_final():
                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)
        if eschema.is_final():
            continue
        for rschema, targetschemas, role in eschema.relation_definitions(True):
            for tschema in targetschemas:
                if rinlined.etype_get(eschema, rschema, role, tschema) and \
                       (libconfig is None or not
                        librinlined.etype_get(eschema, rschema, role, tschema)):
160
161
162
                    add_msg(w, 'add a %s' % tschema,
                            'inlined:%s.%s.%s' % (etype, rschema, role))
                    add_msg(w, 'remove this %s' % tschema,
163
164
165
                            'inlined:%s.%s.%s' % (etype, rschema, role))
                    add_msg(w, 'This %s' % tschema,
                            'inlined:%s.%s.%s' % (etype, rschema, role))
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
                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)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
181
    w('# subject and object forms for each relation type\n')
182
    w('# (no object form for final or symetric relation types)\n')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
183
    w('\n')
184
    for rschema in sorted(schema.relations()):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
185
        rtype = rschema.type
186
187
188
189
190
191
192
193
194
195
        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)
196
        # add context information only for non-metadata rtypes
197
        if rschema not in no_context_rtypes:
198
            libsubjects = librschema and librschema.subjects() or ()
199
            for subjschema in rschema.subjects():
200
201
                if not subjschema in libsubjects:
                    add_msg(w, rtype, subjschema.type)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
202
        if not (schema.rschema(rtype).is_final() or rschema.symetric):
203
            if rschema not in no_context_rtypes:
204
205
206
207
208
209
210
211
                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):
212
213
214
215
        add_msg(w, '%s_description' % objid)
        add_msg(w, objid)

def _iter_vreg_objids(vreg, done, prefix=None):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
216
217
218
219
220
    for reg, objdict in vreg.items():
        for objects in objdict.values():
            for obj in objects:
                objid = '%s_%s' % (reg, obj.id)
                if objid in done:
221
222
223
                    break
                if obj.property_defs:
                    yield objid
Adrien Di Mascio's avatar
Adrien Di Mascio committed
224
                    done.add(objid)
225
                    break
226

227

228
def defined_in_library(etype, rtype, tetype, role):
229
230
    """return true if the given relation definition exists in cubicweb's library
    """
Adrien Di Mascio's avatar
Adrien Di Mascio committed
231
232
    if libschema is None:
        return False
233
    if role == 'subject':
Adrien Di Mascio's avatar
Adrien Di Mascio committed
234
235
236
237
238
239
240
241
242
        subjtype, objtype = etype, tetype
    else:
        subjtype, objtype = tetype, etype
    try:
        return libschema.rschema(rtype).has_rdef(subjtype, objtype)
    except KeyError:
        return False


243
LANGS = ('en', 'fr', 'es')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
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.
262

Adrien Di Mascio's avatar
Adrien Di Mascio committed
263
264
265
    It will regenerate cubicweb/i18n/xx.po files. You'll have then to edit those
    files to add translations of newly added messages.
    """
266
    name = 'i18ncubicweb'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
267
268
269
270
271
272

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


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

Adrien Di Mascio's avatar
Adrien Di Mascio committed
335
336
337
    def run(self, args):
        """run the command with its specific arguments"""
        if args:
338
            cubes = [DevCubeConfiguration.cube_dir(cube) for cube in args]
Adrien Di Mascio's avatar
Adrien Di Mascio committed
339
        else:
340
341
            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
342
343
        update_cubes_catalogs(cubes)

344

Adrien Di Mascio's avatar
Adrien Di Mascio committed
345
346
def update_cubes_catalogs(cubes):
    for cubedir in cubes:
Sylvain Thénault's avatar
Sylvain Thénault committed
347
        toedit = []
Adrien Di Mascio's avatar
Adrien Di Mascio committed
348
        if not isdir(cubedir):
349
            print '-> ignoring %s that is not a directory.' % cubedir
Adrien Di Mascio's avatar
Adrien Di Mascio committed
350
            continue
351
352
353
354
355
        try:
            toedit += update_cube_catalogs(cubedir)
        except Exception:
            import traceback
            traceback.print_exc()
Sylvain Thénault's avatar
Sylvain Thénault committed
356
357
358
359
360
361
362
363
            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
364

365
366
def update_cube_catalogs(cubedir):
    import shutil
367
    import tempfile
368
369
370
371
372
    from logilab.common.fileutils import ensure_fs_mode
    from logilab.common.shellutils import find, rm
    from cubicweb.common.i18n import extract_from_tal, execute
    toedit = []
    cube = basename(normpath(cubedir))
373
    tempdir = tempfile.mkdtemp()
374
    print underline_title('Updating i18n catalogs for cube %s' % cube)
375
    chdir(cubedir)
376
377
378
379
380
381
382
383
    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 = []
384
    print '-> extract schema messages'
385
386
387
388
389
390
391
    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()
392
    print '-> extract TAL messages'
393
394
    tali18nfile = join(tempdir, 'tali18n.py')
    extract_from_tal(find('.', ('.py', '.pt'), blacklist=STD_BLACKLIST+('test',)), tali18nfile)
395
    print '-> extract Javascript messages'
396
397
398
399
400
401
402
403
    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)
404
    print '-> create cube-specific catalog'
405
406
407
408
    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'
409
            % (tmppotfile, ' '.join('"%s"' % f for f in cubefiles)))
410
411
412
    if exists(tmppotfile): # doesn't exists of no translation string found
        potfiles.append(tmppotfile)
    potfile = join(tempdir, 'cube.pot')
413
    print '-> merging %i .pot files:' % len(potfiles)
414
415
    execute('msgcat -o %s %s' % (potfile,
                                 ' '.join('"%s"' % f for f in potfiles)))
416
    print '-> merging main pot file with existing translations:'
417
418
    chdir('i18n')
    for lang in LANGS:
419
        print '-> language', lang
420
421
422
423
        cubepo = '%s.po' % lang
        if not exists(cubepo):
            shutil.copy(potfile, cubepo)
        else:
424
            execute('msgmerge -N -s -o %snew %s %s' % (cubepo, cubepo, potfile))
425
426
427
428
429
430
431
432
            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
433
434
435
436
437
438
class LiveServerCommand(Command):
    """Run a server from within a cube directory.
    """
    name = 'live-server'
    arguments = ''
    options = ()
439

Adrien Di Mascio's avatar
Adrien Di Mascio committed
440
441
442
443
444
445
    def run(self, args):
        """run the command with its specific arguments"""
        from cubicweb.devtools.livetest import runserver
        runserver()


446
class NewCubeCommand(Command):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
447
448
449
450
451
452
453
454
    """Create a new cube.

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

455
    options = (
456
457
458
459
460
        ("directory",
         {'short': 'd', 'type' : 'string', 'metavar': '<cubes directory>',
          'help': 'directory where the new cube should be created',
          }
         ),
461
462
463
464
465
466
        ("verbose",
         {'short': 'v', 'type' : 'yn', 'metavar': '<verbose>',
          'default': 'n',
          'help': 'verbose mode: will ask all possible configuration questions',
          }
         ),
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
        ("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',
          }
         ),
485
486
        )

487

Adrien Di Mascio's avatar
Adrien Di Mascio committed
488
489
490
491
    def run(self, args):
        if len(args) != 1:
            raise BadCommandUsage("exactly one argument (cube name) is expected")
        cubename, = args
492
        verbose = self.get('verbose')
493
        cubesdir = self.get('directory')
494
495
        if not cubesdir:
            cubespath = ServerConfiguration.cubes_search_path()
496
497
498
499
500
            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):
501
            print "-> creating cubes directory", cubesdir
Adrien Di Mascio's avatar
Adrien Di Mascio committed
502
            try:
503
                mkdir(cubesdir)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
504
            except OSError, err:
505
                self.fail("failed to create directory %r\n(%s)" % (cubesdir, err))
506
        cubedir = join(cubesdir, cubename)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
507
508
509
        if exists(cubedir):
            self.fail("%s already exists !" % (cubedir))
        skeldir = join(BASEDIR, 'skeleton')
510
        default_name = 'cubicweb-%s' % cubename.lower()
511
        if verbose:
512
            distname = raw_input('Debian name for your cube ? [%s]): ' % default_name).strip()
513
            if not distname:
514
                distname = default_name
515
            elif not distname.startswith('cubicweb-'):
516
                if ASK.confirm('Do you mean cubicweb-%s ?' % distname):
517
518
                    distname = 'cubicweb-' + distname
        else:
519
            distname = default_name
520

521
522
        longdesc = shortdesc = raw_input('Enter a short description for your cube: ')
        if verbose:
523
            longdesc = raw_input('Enter a long description (leave empty to reuse the short one): ')
524
525
526
527
528
529
        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
530
        else:
531
            dependancies = ''
Adrien Di Mascio's avatar
Adrien Di Mascio committed
532
533
534
535
536
537
        context = {'cubename' : cubename,
                   'distname' : distname,
                   'shortdesc' : shortdesc,
                   'longdesc' : longdesc or shortdesc,
                   'dependancies' : dependancies,
                   'version'  : cubicwebversion,
538
                   'year'  : str(datetime.now().year),
539
540
541
                   'author': self['author'],
                   'author-email': self['author-email'],
                   'author-web-site': self['author-web-site'],
Adrien Di Mascio's avatar
Adrien Di Mascio committed
542
543
544
545
546
547
                   }
        copy_skeleton(skeldir, cubedir, context)

    def _ask_for_dependancies(self):
        includes = []
        for stdtype in ServerConfiguration.available_cubes():
548
549
550
            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
551
                includes.append(stdtype)
552
            if answer == 'type':
553
                includes = splitstrip(raw_input('type dependancies: '))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
554
                break
555
            elif answer == 'skip':
Adrien Di Mascio's avatar
Adrien Di Mascio committed
556
557
                break
        return includes
558

559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576

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

578
579
580
581
582
    def run(self, args):
        if args:
            raise BadCommandUsage("no argument expected")
        import re
        requests = {}
583
        for lineno, line in enumerate(sys.stdin):
584
585
586
            if not ' WHERE ' in line:
                continue
            #sys.stderr.write( line )
587
588
589
            try:
                rql, time = line.split('--')
                rql = re.sub("(\'\w+': \d*)", '', rql)
590
591
                if '{' in rql:
                    rql = rql[:rql.index('{')]
592
593
594
                req = requests.setdefault(rql, [])
                time.strip()
                chunks = time.split()
595
                clocktime = float(chunks[0][1:])
596
                cputime = float(chunks[-3])
597
                req.append( (clocktime, cputime) )
598
            except Exception, exc:
599
                sys.stderr.write('Line %s: %s (%s)\n' % (lineno, exc, line))
600
601
602

        stat = []
        for rql, times in requests.items():
603
604
605
            stat.append( (sum(time[0] for time in times),
                          sum(time[1] for time in times),
                          len(times), rql) )
606
607
608

        stat.sort()
        stat.reverse()
609

610
611
612
613
        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)
614

Adrien Di Mascio's avatar
Adrien Di Mascio committed
615
616
617
register_commands((UpdateCubicWebCatalogCommand,
                   UpdateTemplateCatalogCommand,
                   LiveServerCommand,
618
                   NewCubeCommand,
619
                   ExamineLogCommand,
Adrien Di Mascio's avatar
Adrien Di Mascio committed
620
                   ))