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

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

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

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

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

31

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

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

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

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


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

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

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


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

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

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


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

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

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

237

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


253
LANGS = ('en', 'fr', 'es')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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.
272

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

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


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

Adrien Di Mascio's avatar
Adrien Di Mascio committed
345
346
347
    def run(self, args):
        """run the command with its specific arguments"""
        if args:
348
            cubes = [DevCubeConfiguration.cube_dir(cube) for cube in args]
Adrien Di Mascio's avatar
Adrien Di Mascio committed
349
        else:
350
351
            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
352
353
        update_cubes_catalogs(cubes)

354

Adrien Di Mascio's avatar
Adrien Di Mascio committed
355
356
def update_cubes_catalogs(cubes):
    for cubedir in cubes:
Sylvain Thénault's avatar
Sylvain Thénault committed
357
        toedit = []
Adrien Di Mascio's avatar
Adrien Di Mascio committed
358
        if not isdir(cubedir):
359
            print '-> ignoring %s that is not a directory.' % cubedir
Adrien Di Mascio's avatar
Adrien Di Mascio committed
360
            continue
361
362
363
364
365
        try:
            toedit += update_cube_catalogs(cubedir)
        except Exception:
            import traceback
            traceback.print_exc()
Sylvain Thénault's avatar
Sylvain Thénault committed
366
367
368
369
370
371
372
373
            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
374

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

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


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

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

465
    options = (
466
467
468
469
470
        ("directory",
         {'short': 'd', 'type' : 'string', 'metavar': '<cubes directory>',
          'help': 'directory where the new cube should be created',
          }
         ),
471
472
473
474
475
476
        ("verbose",
         {'short': 'v', 'type' : 'yn', 'metavar': '<verbose>',
          'default': 'n',
          'help': 'verbose mode: will ask all possible configuration questions',
          }
         ),
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
        ("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',
          }
         ),
495
496
        )

497

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

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

    def _ask_for_dependancies(self):
        includes = []
        for stdtype in ServerConfiguration.available_cubes():
558
559
560
            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
561
                includes.append(stdtype)
562
            if answer == 'type':
563
                includes = splitstrip(raw_input('type dependancies: '))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
564
                break
565
            elif answer == 'skip':
Adrien Di Mascio's avatar
Adrien Di Mascio committed
566
567
                break
        return includes
568

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

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

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

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

        stat.sort()
        stat.reverse()
619

620
621
622
623
        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)
624

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