# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr## This file is part of CubicWeb.## CubicWeb is free software: you can redistribute it and/or modify it under the# terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## CubicWeb is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with CubicWeb. If not, see <http://www.gnu.org/licenses/>."""additional cubicweb-ctl commands and command handlers for cubicweb andcubicweb's cubes development"""__docformat__="restructuredtext en"# *ctl module should limit the number of import to be imported as quickly as# possible (for cubicweb-ctl reactivity, necessary for instance for usable bash# completion). So import locally in command helpers.importsysfromdatetimeimportdatetimefromosimportmkdir,chdir,pathasospfromwarningsimportwarnfromlogilab.commonimportSTD_BLACKLISTfromcubicweb.__pkginfo__importversionascubicwebversionfromcubicwebimportCW_SOFTWARE_ROOTasBASEDIR,BadCommandUsagefromcubicweb.cwctlimportCWCTLfromcubicweb.cwconfigimportCubicWebNoAppConfigurationfromcubicweb.toolsutilsimport(SKEL_EXCLUDE,Command,copy_skeleton,underline_title)fromcubicweb.web.webconfigimportWebConfigurationfromcubicweb.server.serverconfigimportServerConfigurationclassDevConfiguration(ServerConfiguration,WebConfiguration):"""dummy config to get full library schema and appobjects for a cube or for cubicweb (without a home) """creating=Truecleanup_interface_sobjects=Falsecubicweb_appobject_path=(ServerConfiguration.cubicweb_appobject_path|WebConfiguration.cubicweb_appobject_path)cube_appobject_path=(ServerConfiguration.cube_appobject_path|WebConfiguration.cube_appobject_path)def__init__(self,*cubes):super(DevConfiguration,self).__init__(cubesandcubes[0]orNone)ifcubes:self._cubes=self.reorder_cubes(self.expand_cubes(cubes,with_recommends=True))self.load_site_cubicweb()else:self._cubes=()@propertydefapphome(self):returnNonedefavailable_languages(self):returnself.cw_languages()defmain_config_file(self):returnNonedefinit_log(self):passdefload_configuration(self):passdefdefault_log_file(self):returnNonedefcleanup_sys_modules(config):# cleanup sys.modules, required when we're updating multiple cubesforname,modinsys.modules.items():ifmodisNone:# duh ? logilab.common.os for instancedelsys.modules[name]continueifnothasattr(mod,'__file__'):continueforpathinconfig.vregistry_path():ifmod.__file__.startswith(path):delsys.modules[name]break# fresh rtagsfromcubicwebimportrtagsfromcubicweb.webimportuicfgrtags.RTAGS[:]=[]reload(uicfg)defgenerate_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 """fromcubicweb.cwvregimportCubicWebVRegistryifcubedir:cube=osp.split(cubedir)[-1]config=DevConfiguration(cube)depcubes=list(config._cubes)depcubes.remove(cube)libconfig=DevConfiguration(*depcubes)else:config=DevConfiguration()cube=libconfig=Nonecleanup_sys_modules(config)schema=config.load_schema(remove_unused_rtypes=False)vreg=CubicWebVRegistry(config)# set_schema triggers objects registrationsvreg.set_schema(schema)w(DEFAULT_POT_HEAD)_generate_schema_pot(w,vreg,schema,libconfig=libconfig)def_generate_schema_pot(w,vreg,schema,libconfig=None):fromcopyimportdeepcopyfromcubicweb.i18nimportadd_msgfromcubicweb.webimportuicfgfromcubicweb.schemaimportNO_I18NCONTEXT,CONSTRAINTSw('# schema pot file, generated on %s\n'%datetime.now().strftime('%Y-%m-%d %H:%M:%S'))w('# \n')w('# singular and plural forms for each entity type\n')w('\n')vregdone=set()iflibconfigisnotNone:fromcubicweb.cwvregimportCubicWebVRegistry,clear_rtag_objectslibschema=libconfig.load_schema(remove_unused_rtypes=False)afs=deepcopy(uicfg.autoform_section)appearsin_addmenu=deepcopy(uicfg.actionbox_appearsin_addmenu)clear_rtag_objects()cleanup_sys_modules(libconfig)libvreg=CubicWebVRegistry(libconfig)libvreg.set_schema(libschema)# trigger objects registrationlibafs=uicfg.autoform_sectionlibappearsin_addmenu=uicfg.actionbox_appearsin_addmenu# prefill vregdone setlist(_iter_vreg_objids(libvreg,vregdone))else:libschema={}afs=uicfg.autoform_sectionappearsin_addmenu=uicfg.actionbox_appearsin_addmenuforcstrtypeinCONSTRAINTS:add_msg(w,cstrtype)done=set()foreschemainsorted(schema.entities()):etype=eschema.typeifetypenotinlibschema:add_msg(w,etype)add_msg(w,'%s_plural'%etype)ifnoteschema.final:add_msg(w,'This %s'%etype)add_msg(w,'New %s'%etype)ifeschema.descriptionandnoteschema.descriptionindone:done.add(eschema.description)add_msg(w,eschema.description)ifeschema.final:continueforrschema,targetschemas,roleineschema.relation_definitions(True):ifrschema.final:continuefortschemaintargetschemas:fsections=afs.etype_get(eschema,rschema,role,tschema)if'main_inlined'infsectionsand \(libconfigisNoneornot'main_inlined'inlibafs.etype_get(eschema,rschema,role,tschema)):add_msg(w,'add a %s'%tschema,'inlined:%s.%s.%s'%(etype,rschema,role))add_msg(w,str(tschema),'inlined:%s.%s.%s'%(etype,rschema,role))ifappearsin_addmenu.etype_get(eschema,rschema,role,tschema):iflibconfigisnotNoneandlibappearsin_addmenu.etype_get(eschema,rschema,role,tschema):ifeschemainlibschemaandtschemainlibschema:continueifrole=='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)# XXX also generate "creating ...' messages for actions in the# addrelated submenuw('# subject and object forms for each relation type\n')w('# (no object form for final or symmetric relation types)\n')w('\n')forrschemainsorted(schema.relations()):rtype=rschema.typeifrtypenotinlibschema:# bw compat, necessary until all translation of relation are done# properly...add_msg(w,rtype)ifrschema.descriptionandrschema.descriptionnotindone:done.add(rschema.description)add_msg(w,rschema.description)done.add(rtype)librschema=Noneelse:librschema=libschema.rschema(rtype)# add context information only for non-metadata rtypesifrschemanotinNO_I18NCONTEXT:libsubjects=librschemaandlibrschema.subjects()or()forsubjschemainrschema.subjects():ifnotsubjschemainlibsubjects:add_msg(w,rtype,subjschema.type)ifnot(schema.rschema(rtype).finalorrschema.symmetric):ifrschemanotinNO_I18NCONTEXT:libobjects=librschemaandlibrschema.objects()or()forobjschemainrschema.objects():ifnotobjschemainlibobjects:add_msg(w,'%s_object'%rtype,objschema.type)ifrtypenotinlibschema:# bw compat, necessary until all translation of relation are# done properly...add_msg(w,'%s_object'%rtype)forobjidin_iter_vreg_objids(vreg,vregdone):add_msg(w,'%s_description'%objid)add_msg(w,objid)def_iter_vreg_objids(vreg,done):forreg,objdictinvreg.items():ifregin('boxes','contentnavigation'):continueforobjectsinobjdict.values():forobjinobjects:objid='%s_%s'%(reg,obj.__regid__)ifobjidindone:breaktry:# XXX < 3.6 bw compatpdefs=obj.property_defsexceptAttributeError:pdefs=getattr(obj,'cw_property_defs',{})ifpdefs:yieldobjiddone.add(objid)breakDEFAULT_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"'''%cubicwebversionclassUpdateCubicWebCatalogCommand(Command):"""Update i18n catalogs for cubicweb library. It will regenerate cubicweb/i18n/xx.po files. You'll have then to edit those files to add translations of newly added messages. """name='i18ncubicweb'min_args=max_args=0defrun(self,args):"""run the command with its specific arguments"""importshutilimporttempfileimportyamsfromlogilab.common.fileutilsimportensure_fs_modefromlogilab.common.shellutilsimportglobfind,find,rmfromlogilab.common.modutilsimportget_module_filesfromcubicweb.i18nimportextract_from_tal,executetempdir=tempfile.mkdtemp()cwi18ndir=WebConfiguration.i18n_lib_dir()print'-> extract schema messages.'schemapot=osp.join(tempdir,'schema.pot')potfiles=[schemapot]potfiles.append(schemapot)# explicit close necessary else the file may not be yet flushed when# we'll using it belowschemapotstream=file(schemapot,'w')generate_schema_pot(schemapotstream.write,cubedir=None)schemapotstream.close()print'-> extract TAL messages.'tali18nfile=osp.join(tempdir,'tali18n.py')extract_from_tal(find(osp.join(BASEDIR,'web'),('.py','.pt')),tali18nfile)print'-> generate .pot files.'pyfiles=get_module_files(BASEDIR)pyfiles+=globfind(osp.join(BASEDIR,'misc','migration'),'*.py')schemafiles=globfind(osp.join(BASEDIR,'schemas'),'*.py')jsfiles=globfind(osp.join(BASEDIR,'web'),'cub*.js')forid,files,langin[('pycubicweb',pyfiles,None),('schemadescr',schemafiles,None),('yams',get_module_files(yams.__path__[0]),None),('tal',[tali18nfile],None),('js',jsfiles,'java'),]:cmd='xgettext --no-location --omit-header -k_ -o %s%s'iflangisnotNone:cmd+=' -L %s'%langpotfile=osp.join(tempdir,'%s.pot'%id)execute(cmd%(potfile,' '.join('"%s"'%fforfinfiles)))ifosp.exists(potfile):potfiles.append(potfile)else:print'-> WARNING: %s file was not generated'%potfileprint'-> merging %i .pot files'%len(potfiles)cubicwebpot=osp.join(tempdir,'cubicweb.pot')execute('msgcat -o %s%s'%(cubicwebpot,' '.join('"%s"'%fforfinpotfiles)))print'-> merging main pot file with existing translations.'chdir(cwi18ndir)toedit=[]forlanginCubicWebNoAppConfiguration.cw_languages():target='%s.po'%langexecute('msgmerge -N --sort-output -o "%snew" "%s" "%s"'%(target,target,cubicwebpot))ensure_fs_mode(target)shutil.move('%snew'%target,target)toedit.append(osp.abspath(target))# cleanuprm(tempdir)# instructions pour la suiteprint'-> regenerated CubicWeb\'s .po catalogs.'print'\nYou can now edit the following files:'print'* '+'\n* '.join(toedit)print'when you are done, run "cubicweb-ctl i18ncube yourcube".'classUpdateCubeCatalogCommand(Command):"""Update i18n catalogs for cubes. If no cube is specified, update catalogs of all registered cubes. """name='i18ncube'arguments='[<cube>...]'defrun(self,args):"""run the command with its specific arguments"""ifargs:cubes=[DevConfiguration.cube_dir(cube)forcubeinargs]else:cubes=[DevConfiguration.cube_dir(cube)forcubeinDevConfiguration.available_cubes()]cubes=[cubepathforcubepathincubesifosp.exists(osp.join(cubepath,'i18n'))]update_cubes_catalogs(cubes)defupdate_cubes_catalogs(cubes):forcubedirincubes:ifnotosp.isdir(cubedir):print'-> ignoring %s that is not a directory.'%cubedircontinuetry:toedit=update_cube_catalogs(cubedir)exceptException:importtracebacktraceback.print_exc()print'-> error while updating catalogs for cube',cubedirelse:# instructions pour la suiteiftoedit:print'-> regenerated .po catalogs for cube %s.'%cubedirprint'\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.')defupdate_cube_catalogs(cubedir):importshutilimporttempfilefromlogilab.common.fileutilsimportensure_fs_modefromlogilab.common.shellutilsimportfind,rmfromcubicweb.i18nimportextract_from_tal,executecube=osp.basename(osp.normpath(cubedir))tempdir=tempfile.mkdtemp()printunderline_title('Updating i18n catalogs for cube %s'%cube)chdir(cubedir)ifosp.exists(osp.join('i18n','entities.pot')):warn('entities.pot is deprecated, rename file to static-messages.pot (%s)'%osp.join('i18n','entities.pot'),DeprecationWarning)potfiles=[osp.join('i18n','entities.pot')]elifosp.exists(osp.join('i18n','static-messages.pot')):potfiles=[osp.join('i18n','static-messages.pot')]else:potfiles=[]print'-> extract schema messages'schemapot=osp.join(tempdir,'schema.pot')potfiles.append(schemapot)# explicit close necessary else the file may not be yet flushed when# we'll using it belowschemapotstream=file(schemapot,'w')generate_schema_pot(schemapotstream.write,cubedir)schemapotstream.close()print'-> extract TAL messages'tali18nfile=osp.join(tempdir,'tali18n.py')ptfiles=find('.',('.py','.pt'),blacklist=STD_BLACKLIST+('test',))extract_from_tal(ptfiles,tali18nfile)print'-> extract Javascript messages'jsfiles=[jsfileforjsfileinfind('.','.js')ifosp.basename(jsfile).startswith('cub')]ifjsfiles:tmppotfile=osp.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 translateifosp.exists(tmppotfile):potfiles.append(tmppotfile)print'-> create cube-specific catalog'tmppotfile=osp.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('"%s"'%fforfincubefiles)))ifosp.exists(tmppotfile):# doesn't exists of no translation string foundpotfiles.append(tmppotfile)potfile=osp.join(tempdir,'cube.pot')print'-> merging %i .pot files:'%len(potfiles)execute('msgcat -o %s%s'%(potfile,' '.join('"%s"'%fforfinpotfiles)))ifnotosp.exists(potfile):print'no message catalog for cube',cube,'nothing to translate'# cleanuprm(tempdir)return()print'-> merging main pot file with existing translations:'chdir('i18n')toedit=[]forlanginCubicWebNoAppConfiguration.cw_languages():print'-> language',langcubepo='%s.po'%langifnotosp.exists(cubepo):shutil.copy(potfile,cubepo)else:execute('msgmerge -N -s -o %snew %s%s'%(cubepo,cubepo,potfile))ensure_fs_mode(cubepo)shutil.move('%snew'%cubepo,cubepo)toedit.append(osp.abspath(cubepo))# cleanuprm(tempdir)returntoedit# XXX totally broken, fix it# class LiveServerCommand(Command):# """Run a server from within a cube directory.# """# name = 'live-server'# arguments = ''# options = ()# def run(self, args):# """run the command with its specific arguments"""# from cubicweb.devtools.livetest import runserver# runserver()classNewCubeCommand(Command):"""Create a new cube. <cubename> the name of the new cube. It should be a valid python module name. """name='newcube'arguments='<cubename>'min_args=max_args=1options=(("layout",{'short':'L','type':'choice','metavar':'<cube layout>','default':'simple','choices':('simple','full'),'help':'cube layout. You\'ll get a minimal cube with the "simple" \layout, and a full featured cube with "full" layout.',}),("directory",{'short':'d','type':'string','metavar':'<cubes directory>','help':'directory where the new cube should be created',}),("verbose",{'short':'v','type':'yn','metavar':'<verbose>','default':'n','help':'verbose mode: will ask all possible configuration questions',}),("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',}),("license",{'short':'l','type':'choice','metavar':'<license>','default':'LGPL','choices':('GPL','LGPL',''),'help':'cube license',}),)LICENSES={'LGPL':'''\# This program is free software: you can redistribute it and/or modify it under# the terms of the GNU Lesser General Public License as published by the Free# Software Foundation, either version 2.1 of the License, or (at your option)# any later version.## This program is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more# details.## You should have received a copy of the GNU Lesser General Public License along# with this program. If not, see <http://www.gnu.org/licenses/>.''','GPL':'''\# This program is free software: you can redistribute it and/or modify it under# the terms of the GNU General Public License as published by the Free Software# Foundation, either version 2.1 of the License, or (at your option) any later# version.## This program is distributed in the hope that it will be useful, but WITHOUT# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.## You should have received a copy of the GNU General Public License along with# this program. If not, see <http://www.gnu.org/licenses/>.''','':'# INSERT LICENSE HERE'}defrun(self,args):importrefromlogilab.common.shellutilsimportASKcubename=args[0]ifnotre.match('[_A-Za-z][_A-Za-z0-9]*$',cubename):raiseBadCommandUsage('cube name must be a valid python module name')verbose=self.get('verbose')cubesdir=self.get('directory')ifnotcubesdir:cubespath=ServerConfiguration.cubes_search_path()iflen(cubespath)>1:raiseBadCommandUsage("can't guess directory where to put the new cube."" Please specify it using the --directory option")cubesdir=cubespath[0]ifnotosp.isdir(cubesdir):print"-> creating cubes directory",cubesdirtry:mkdir(cubesdir)exceptOSError,err:self.fail("failed to create directory %r\n(%s)"%(cubesdir,err))cubedir=osp.join(cubesdir,cubename)ifosp.exists(cubedir):self.fail("%s already exists !"%cubedir)skeldir=osp.join(BASEDIR,'skeleton')default_name='cubicweb-%s'%cubename.lower().replace('_','-')ifverbose:distname=raw_input('Debian name for your cube ? [%s]): '%default_name).strip()ifnotdistname:distname=default_nameelifnotdistname.startswith('cubicweb-'):ifASK.confirm('Do you mean cubicweb-%s ?'%distname):distname='cubicweb-'+distnameelse:distname=default_nameifnotre.match('[a-z][-a-z0-9]*$',distname):raiseBadCommandUsage('cube distname should be a valid debian package name')longdesc=shortdesc=raw_input('Enter a short description for your cube: ')ifverbose:longdesc=raw_input('Enter a long description (leave empty to reuse the short one): ')dependencies={'cubicweb':'>= %s'%cubicwebversion}ifverbose:dependencies.update(self._ask_for_dependencies())context={'cubename':cubename,'distname':distname,'shortdesc':shortdesc,'longdesc':longdescorshortdesc,'dependencies':dependencies,'version':cubicwebversion,'year':str(datetime.now().year),'author':self['author'],'author-email':self['author-email'],'author-web-site':self['author-web-site'],'license':self['license'],'long-license':self.LICENSES[self['license']],}exclude=SKEL_EXCLUDEifself['layout']=='simple':exclude+=('sobjects.py*','precreate.py*','realdb_test*','cubes.*','uiprops.py*')copy_skeleton(skeldir,cubedir,context,exclude=exclude)def_ask_for_dependencies(self):fromlogilab.common.shellutilsimportASKfromlogilab.common.textutilsimportsplitstripdepcubes=[]forcubeinServerConfiguration.available_cubes():answer=ASK.ask("Depends on cube %s? "%cube,('N','y','skip','type'),'N')ifanswer=='y':depcubes.append(cube)ifanswer=='type':depcubes=splitstrip(raw_input('type dependencies: '))breakelifanswer=='skip':breakreturndict(('cubicweb-'+cube,ServerConfiguration.cube_version(cube))forcubeindepcubes)classExamineLogCommand(Command):"""Examine a rql log file. 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. """arguments='rql.log'name='exlog'options=()defrun(self,args):importrerequests={}forfilepathinargs:try:stream=file(filepath)exceptOSError,ex:raiseBadCommandUsage("can't open rql log file %s: %s"%(filepath,ex))forlineno,lineinenumerate(stream):ifnot' WHERE 'inline:continuetry:rql,time=line.split('--')rql=re.sub("(\'\w+': \d*)",'',rql)if'{'inrql:rql=rql[:rql.index('{')]req=requests.setdefault(rql,[])time.strip()chunks=time.split()clocktime=float(chunks[0][1:])cputime=float(chunks[-3])req.append((clocktime,cputime))exceptException,exc:sys.stderr.write('Line %s: %s (%s)\n'%(lineno,exc,line))stat=[]forrql,timesinrequests.iteritems():stat.append((sum(time[0]fortimeintimes),sum(time[1]fortimeintimes),len(times),rql))stat.sort()stat.reverse()total_time=sum(clocktimeforclocktime,cputime,occ,rqlinstat)*0.01print'Percentage;Cumulative Time (clock);Cumulative Time (CPU);Occurences;Query'forclocktime,cputime,occ,rqlinstat:print'%.2f;%.2f;%.2f;%s;%s'%(clocktime/total_time,clocktime,cputime,occ,rql)classGenerateSchema(Command):"""Generate schema image for the given cube"""name="schema"arguments='<cube>'min_args=max_args=1options=[('output-file',{'type':'file','default':None,'metavar':'<file>','short':'o','help':'output image file','input':False,}),('viewer',{'type':'string','default':None,'short':"d",'metavar':'<cmd>','help':'command use to view the generated file (empty for none)',}),('show-meta',{'action':'store_true','default':False,'short':"m",'metavar':"<yN>",'help':'include meta and internal entities in schema',}),('show-workflow',{'action':'store_true','default':False,'short':"w",'metavar':"<yN>",'help':'include workflow entities in schema',}),('show-cw-user',{'action':'store_true','default':False,'metavar':"<yN>",'help':'include cubicweb user entities in schema',}),('exclude-type',{'type':'string','default':'','short':"x",'metavar':"<types>",'help':'coma separated list of entity types to remove from view',}),('include-type',{'type':'string','default':'','short':"i",'metavar':"<types>",'help':'coma separated list of entity types to include in view',}),]defrun(self,args):fromsubprocessimportPopenfromtempfileimportNamedTemporaryFilefromlogilab.common.textutilsimportsplitstripfromyamsimportschema2dot,BASE_TYPESfromcubicweb.schemaimport(META_RTYPES,SCHEMA_TYPES,SYSTEM_RTYPES,WORKFLOW_TYPES,INTERNAL_TYPES)cubes=splitstrip(args[0])dev_conf=DevConfiguration(*cubes)schema=dev_conf.load_schema()out,viewer=self['output-file'],self['viewer']ifoutisNone:tmp_file=NamedTemporaryFile(suffix=".svg")out=tmp_file.nameskiptypes=BASE_TYPES|SCHEMA_TYPESifnotself['show-meta']:skiptypes|=META_RTYPES|SYSTEM_RTYPES|INTERNAL_TYPESifnotself['show-workflow']:skiptypes|=WORKFLOW_TYPESifnotself['show-cw-user']:skiptypes|=set(('CWUser','CWGroup','EmailAddress'))skiptypes|=set(self['exclude-type'].split(','))skiptypes-=set(self['include-type'].split(','))schema2dot.schema2dot(schema,out,skiptypes=skiptypes)ifviewer:p=Popen((viewer,out))p.wait()classGenerateQUnitHTML(Command):"""Generate a QUnit html file to see test in your browser"""name="qunit-html"arguments='<test file> [<dependancy js file>...]'defrun(self,args):fromcubicweb.devtools.qunitimportmake_qunit_htmlprintmake_qunit_html(args[0],args[1:])forcmdclsin(UpdateCubicWebCatalogCommand,UpdateCubeCatalogCommand,#LiveServerCommand,NewCubeCommand,ExamineLogCommand,GenerateSchema,GenerateQUnitHTML,):CWCTL.register(cmdcls)