devctl.py 23.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
Aurelien Campeas's avatar
Aurelien Campeas committed
13
from os import mkdir, chdir
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
Adrien Di Mascio's avatar
Adrien Di Mascio committed
26
27
28
from cubicweb.web.webconfig import WebConfiguration
from cubicweb.server.serverconfig import ServerConfiguration

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

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

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

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


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

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

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

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


112
def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
113
    from cubicweb.common.i18n import add_msg
114
    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
115
116
117
    w('# \n')
    w('# singular and plural forms for each entity type\n')
    w('\n')
118
    if libconfig is not None:
119
        libschema = libconfig.load_schema(remove_unused_rtypes=False)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
120
121
        entities = [e for e in schema.entities() if not e in libschema]
    else:
122
        libschema = None
Adrien Di Mascio's avatar
Adrien Di Mascio committed
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
        entities = schema.entities()
    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)
            add_msg(w, 'add a %s' % etype)
            add_msg(w, 'remove this %s' % etype)
        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')
140
    if libconfig is not None:
Adrien Di Mascio's avatar
Adrien Di Mascio committed
141
142
143
144
145
146
        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
        add_msg(w, rtype)
147
        done.add(rtype)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
148
149
150
151
152
153
154
        if not (schema.rschema(rtype).is_final() or rschema.symetric):
            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')
Aurelien Campeas's avatar
Aurelien Campeas committed
155
    actionbox = vreg['boxes']['edit_box'][0]
Adrien Di Mascio's avatar
Adrien Di Mascio committed
156
157
158
    for eschema in schema.entities():
        if eschema.is_final():
            continue
159
        for role, rschemas in (('subject', eschema.subject_relations()),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
160
161
162
163
                            ('object', eschema.object_relations())):
            for rschema in rschemas:
                if rschema.is_final():
                    continue
164
165
                if libconfig is not None:
                    librschema = libschema.get(rschema)
166
                for teschema in rschema.targets(eschema, role):
167
                    if libconfig is not None and librschema is not None:
168
                        if role == 'subject':
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
169
                            subjtype, objtype = eschema, teschema
170
                        else:
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
171
                            subjtype, objtype = teschema, eschema
172
                        if librschema.has_rdef(subjtype, objtype):
173
                            continue
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
174
175
                    if actionbox.appearsin_addmenu.etype_get(eschema, rschema,
                                                             role, teschema):
176
177
178
179
180
                        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
181
                        else:
182
183
184
185
                            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
186
187
                        add_msg(w, label)
                        add_msg(w, label2)
188
    #cube = (cube and 'cubes.%s.' % cube or 'cubicweb.')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
189
    done = set()
190
    if libconfig is not None:
191
192
        from cubicweb.cwvreg import CubicWebVRegistry
        libvreg = CubicWebVRegistry(libconfig)
193
194
195
196
197
198
199
200
        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
201
202
203
204
205
    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:
206
207
208
                    break
                if obj.property_defs:
                    yield objid
Adrien Di Mascio's avatar
Adrien Di Mascio committed
209
                    done.add(objid)
210
                    break
211

212

213
def defined_in_library(etype, rtype, tetype, role):
214
215
    """return true if the given relation definition exists in cubicweb's library
    """
Adrien Di Mascio's avatar
Adrien Di Mascio committed
216
217
    if libschema is None:
        return False
218
    if role == 'subject':
Adrien Di Mascio's avatar
Adrien Di Mascio committed
219
220
221
222
223
224
225
226
227
        subjtype, objtype = etype, tetype
    else:
        subjtype, objtype = tetype, etype
    try:
        return libschema.rschema(rtype).has_rdef(subjtype, objtype)
    except KeyError:
        return False


228
LANGS = ('en', 'fr', 'es')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
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.
247

Adrien Di Mascio's avatar
Adrien Di Mascio committed
248
249
250
    It will regenerate cubicweb/i18n/xx.po files. You'll have then to edit those
    files to add translations of newly added messages.
    """
251
    name = 'i18ncubicweb'
Adrien Di Mascio's avatar
Adrien Di Mascio committed
252
253
254
255
256
257

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


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

Adrien Di Mascio's avatar
Adrien Di Mascio committed
320
321
322
    def run(self, args):
        """run the command with its specific arguments"""
        if args:
323
            cubes = [DevCubeConfiguration.cube_dir(cube) for cube in args]
Adrien Di Mascio's avatar
Adrien Di Mascio committed
324
        else:
325
326
            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
327
328
        update_cubes_catalogs(cubes)

329

Adrien Di Mascio's avatar
Adrien Di Mascio committed
330
331
def update_cubes_catalogs(cubes):
    for cubedir in cubes:
Sylvain Thénault's avatar
Sylvain Thénault committed
332
        toedit = []
Adrien Di Mascio's avatar
Adrien Di Mascio committed
333
        if not isdir(cubedir):
334
            print '-> ignoring %s that is not a directory.' % cubedir
Adrien Di Mascio's avatar
Adrien Di Mascio committed
335
            continue
336
337
338
339
340
        try:
            toedit += update_cube_catalogs(cubedir)
        except Exception:
            import traceback
            traceback.print_exc()
Sylvain Thénault's avatar
Sylvain Thénault committed
341
342
343
344
345
346
347
348
            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
349

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

Adrien Di Mascio's avatar
Adrien Di Mascio committed
424
425
426
427
428
429
    def run(self, args):
        """run the command with its specific arguments"""
        from cubicweb.devtools.livetest import runserver
        runserver()


430
class NewCubeCommand(Command):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
431
432
433
434
435
436
437
438
    """Create a new cube.

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

439
    options = (
440
441
442
443
444
        ("directory",
         {'short': 'd', 'type' : 'string', 'metavar': '<cubes directory>',
          'help': 'directory where the new cube should be created',
          }
         ),
445
446
447
448
449
450
        ("verbose",
         {'short': 'v', 'type' : 'yn', 'metavar': '<verbose>',
          'default': 'n',
          'help': 'verbose mode: will ask all possible configuration questions',
          }
         ),
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
        ("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',
          }
         ),
469
470
        )

471

Adrien Di Mascio's avatar
Adrien Di Mascio committed
472
473
474
475
    def run(self, args):
        if len(args) != 1:
            raise BadCommandUsage("exactly one argument (cube name) is expected")
        cubename, = args
476
477
        #if ServerConfiguration.mode != "dev":
        #    self.fail("you can only create new cubes in development mode")
478
        verbose = self.get('verbose')
479
        cubesdir = self.get('directory')
480
481
        if not cubesdir:
            cubespath = ServerConfiguration.cubes_search_path()
482
483
484
485
486
            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):
487
            print "-> creating cubes directory", cubesdir
Adrien Di Mascio's avatar
Adrien Di Mascio committed
488
            try:
489
                mkdir(cubesdir)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
490
            except OSError, err:
491
                self.fail("failed to create directory %r\n(%s)" % (cubesdir, err))
492
        cubedir = join(cubesdir, cubename)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
493
494
495
        if exists(cubedir):
            self.fail("%s already exists !" % (cubedir))
        skeldir = join(BASEDIR, 'skeleton')
496
        default_name = 'cubicweb-%s' % cubename.lower()
497
        if verbose:
498
            distname = raw_input('Debian name for your cube ? [%s]): ' % default_name).strip()
499
            if not distname:
500
                distname = default_name
501
            elif not distname.startswith('cubicweb-'):
502
                if ASK.confirm('Do you mean cubicweb-%s ?' % distname):
503
504
                    distname = 'cubicweb-' + distname
        else:
505
            distname = default_name
506

507
508
        longdesc = shortdesc = raw_input('Enter a short description for your cube: ')
        if verbose:
509
            longdesc = raw_input('Enter a long description (leave empty to reuse the short one): ')
510
511
512
513
514
515
        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
516
        else:
517
            dependancies = ''
Adrien Di Mascio's avatar
Adrien Di Mascio committed
518
519
520
521
522
523
        context = {'cubename' : cubename,
                   'distname' : distname,
                   'shortdesc' : shortdesc,
                   'longdesc' : longdesc or shortdesc,
                   'dependancies' : dependancies,
                   'version'  : cubicwebversion,
524
                   'year'  : str(datetime.now().year),
525
526
527
                   'author': self['author'],
                   'author-email': self['author-email'],
                   'author-web-site': self['author-web-site'],
Adrien Di Mascio's avatar
Adrien Di Mascio committed
528
529
530
531
532
533
                   }
        copy_skeleton(skeldir, cubedir, context)

    def _ask_for_dependancies(self):
        includes = []
        for stdtype in ServerConfiguration.available_cubes():
534
535
536
            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
537
                includes.append(stdtype)
538
            if answer == 'type':
539
                includes = splitstrip(raw_input('type dependancies: '))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
540
                break
541
            elif answer == 'skip':
Adrien Di Mascio's avatar
Adrien Di Mascio committed
542
543
                break
        return includes
544

545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562

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

564
565
566
567
568
    def run(self, args):
        if args:
            raise BadCommandUsage("no argument expected")
        import re
        requests = {}
569
        for lineno, line in enumerate(sys.stdin):
570
571
572
            if not ' WHERE ' in line:
                continue
            #sys.stderr.write( line )
573
574
575
            try:
                rql, time = line.split('--')
                rql = re.sub("(\'\w+': \d*)", '', rql)
576
577
                if '{' in rql:
                    rql = rql[:rql.index('{')]
578
579
580
                req = requests.setdefault(rql, [])
                time.strip()
                chunks = time.split()
581
                clocktime = float(chunks[0][1:])
582
                cputime = float(chunks[-3])
583
                req.append( (clocktime, cputime) )
584
            except Exception, exc:
585
                sys.stderr.write('Line %s: %s (%s)\n' % (lineno, exc, line))
586
587
588

        stat = []
        for rql, times in requests.items():
589
590
591
            stat.append( (sum(time[0] for time in times),
                          sum(time[1] for time in times),
                          len(times), rql) )
592
593
594

        stat.sort()
        stat.reverse()
595

596
597
598
599
        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)
600

Adrien Di Mascio's avatar
Adrien Di Mascio committed
601
602
603
register_commands((UpdateCubicWebCatalogCommand,
                   UpdateTemplateCatalogCommand,
                   LiveServerCommand,
604
                   NewCubeCommand,
605
                   ExamineLogCommand,
Adrien Di Mascio's avatar
Adrien Di Mascio committed
606
                   ))