devctl.py 24.1 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 warnings import warn
Adrien Di Mascio's avatar
Adrien Di Mascio committed
16
17
18

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

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

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

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

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

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


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

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

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

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


113
def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
114
    from cubicweb.common.i18n import add_msg
115
    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
116
117
118
    w('# \n')
    w('# singular and plural forms for each entity type\n')
    w('\n')
119
    if libconfig is not None:
120
        libschema = libconfig.load_schema(remove_unused_rtypes=False)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
121
122
123
        entities = [e for e in schema.entities() if not e in libschema]
    else:
        entities = schema.entities()
124
    rinlined = uicfg.autoform_is_inlined
Adrien Di Mascio's avatar
Adrien Di Mascio committed
125
126
127
128
129
130
131
132
    done = set()
    for eschema in sorted(entities):
        etype = eschema.type
        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)
133
134
135
136
137
138
139
140
            for rschema, targetschemas, role in eschema.relation_definitions(True):
                targetschemas = [tschema for tschema in targetschemas
                                 if rinlined.etype_get(eschema, rschema, role, tschema)]
                for tschema in targetschemas:
                    add_msg(w, 'add a %s' % tschema,
                            'inlined:%s.%s.%s' % (etype, rschema, role))
                    add_msg(w, 'remove this %s' % tschema,
                            'inlined:%s:%s:%s' % (etype, rschema, role))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
141
142
143
144
145
146
        if eschema.description and not eschema.description in done:
            done.add(eschema.description)
            add_msg(w, eschema.description)
    w('# subject and object forms for each relation type\n')
    w('# (no object form for final relation types)\n')
    w('\n')
147
    if libconfig is not None:
Adrien Di Mascio's avatar
Adrien Di Mascio committed
148
149
150
151
152
        relations = [r for r in schema.relations() if not r in libschema]
    else:
        relations = schema.relations()
    for rschema in sorted(set(relations)):
        rtype = rschema.type
153
154
        for subjschema in rschema.subjects():
            add_msg(w, rtype, subjschema.type)
155
156
            # bw compat, necessary until all translation of relation are done properly...
        add_msg(w, rtype)
157
        done.add(rtype)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
158
        if not (schema.rschema(rtype).is_final() or rschema.symetric):
159
160
161
            for objschema in rschema.objects():
                add_msg(w, '%s_object' % rtype, objschema.type)
            # bw compat, necessary until all translation of relation are done properly...
Adrien Di Mascio's avatar
Adrien Di Mascio committed
162
163
164
165
166
167
            add_msg(w, '%s_object' % rtype)
        if rschema.description and rschema.description not in done:
            done.add(rschema.description)
            add_msg(w, rschema.description)
    w('# add related box generated message\n')
    w('\n')
168
    appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
Adrien Di Mascio's avatar
Adrien Di Mascio committed
169
170
171
    for eschema in schema.entities():
        if eschema.is_final():
            continue
172
        for role, rschemas in (('subject', eschema.subject_relations()),
173
                               ('object', eschema.object_relations())):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
174
175
176
            for rschema in rschemas:
                if rschema.is_final():
                    continue
177
178
                if libconfig is not None:
                    librschema = libschema.get(rschema)
179
                for teschema in rschema.targets(eschema, role):
180
                    if libconfig is not None and librschema is not None:
181
                        if role == 'subject':
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
182
                            subjtype, objtype = eschema, teschema
183
                        else:
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
184
                            subjtype, objtype = teschema, eschema
185
                        if librschema.has_rdef(subjtype, objtype):
186
                            continue
187
188
                    if appearsin_addmenu.etype_get(eschema, rschema, role,
                                                   teschema):
189
190
191
192
193
                        if role == 'subject':
                            label = 'add %s %s %s %s' % (eschema, rschema,
                                                         teschema, role)
                            label2 = "creating %s (%s %%(linkto)s %s %s)" % (
                                teschema, eschema, rschema, teschema)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
194
                        else:
195
196
197
198
                            label = 'add %s %s %s %s' % (teschema, rschema,
                                                         eschema, role)
                            label2 = "creating %s (%s %s %s %%(linkto)s)" % (
                                teschema, teschema, rschema, eschema)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
199
200
                        add_msg(w, label)
                        add_msg(w, label2)
201
    #cube = (cube and 'cubes.%s.' % cube or 'cubicweb.')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
202
    done = set()
203
    if libconfig is not None:
204
205
        from cubicweb.cwvreg import CubicWebVRegistry
        libvreg = CubicWebVRegistry(libconfig)
206
207
208
209
210
211
212
213
        libvreg.set_schema(libschema) # trigger objects registration
        # prefill done set
        list(_iter_vreg_objids(libvreg, done))
    for objid in _iter_vreg_objids(vreg, done):
        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
214
215
216
217
218
    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:
219
220
221
                    break
                if obj.property_defs:
                    yield objid
Adrien Di Mascio's avatar
Adrien Di Mascio committed
222
                    done.add(objid)
223
                    break
224

225

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


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

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

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


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

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

342

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

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

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


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

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

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

485

Adrien Di Mascio's avatar
Adrien Di Mascio committed
486
487
488
489
    def run(self, args):
        if len(args) != 1:
            raise BadCommandUsage("exactly one argument (cube name) is expected")
        cubename, = args
490
491
        #if ServerConfiguration.mode != "dev":
        #    self.fail("you can only create new cubes in development mode")
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
                   ))