diff -r 000000000000 -r b97547f5f1fa cwconfig.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cwconfig.py Wed Nov 05 15:52:50 2008 +0100 @@ -0,0 +1,856 @@ +"""common configuration utilities for cubicweb + +:organization: Logilab +:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +""" +__docformat__ = "restructuredtext en" + +import sys +import os +import logging +from os.path import exists, join, expanduser, abspath, basename + +from logilab.common.decorators import cached +from logilab.common.configuration import (Configuration, Method, + ConfigurationMixIn, merge_options) + +from cubicweb import CW_SOFTWARE_ROOT, CW_MIGRATION_MAP, ConfigurationError +from cubicweb.toolsutils import env_path, read_config, create_dir + +CONFIGURATIONS = [] + +_ = unicode + +class metaconfiguration(type): + """metaclass to automaticaly register configuration""" + def __new__(mcs, name, bases, classdict): + cls = super(metaconfiguration, mcs).__new__(mcs, name, bases, classdict) + if classdict.get('name'): + CONFIGURATIONS.append(cls) + return cls + +def configuration_cls(name): + """return the configuration class registered with the given name""" + try: + return [c for c in CONFIGURATIONS if c.name == name][0] + except IndexError: + raise ConfigurationError('no such config %r (check it exists with "cubicweb-ctl list")' % name) + +def possible_configurations(directory): + """return a list of installed configurations in a directory + according to *-ctl files + """ + return [name for name in ('repository', 'twisted', 'all-in-one') + if exists(join(directory, '%s.conf' % name))] + +def guess_configuration(directory): + """try to guess the configuration to use for a directory. If multiple + configurations are found, ConfigurationError is raised + """ + modes = possible_configurations(directory) + if len(modes) != 1: + raise ConfigurationError('unable to guess configuration from %r %s' + % (directory, modes)) + return modes[0] + +# XXX generate this according to the configuration (repository/all-in-one/web) +VREGOPTIONS = [] +for registry in ('etypes', 'hooks', 'controllers', 'actions', 'components', + 'views', 'templates', 'boxes', 'contentnavigation', 'urlrewriting', + 'facets'): + VREGOPTIONS.append(('disable-%s'%registry, + {'type' : 'csv', 'default': (), + 'help': 'list of identifier of application objects from the %s registry to disable'%registry, + 'group': 'appobjects', 'inputlevel': 2, + })) +VREGOPTIONS = tuple(VREGOPTIONS) + +# persistent options definition +PERSISTENT_OPTIONS = ( + ('encoding', + {'type' : 'string', + 'default': 'UTF-8', + 'help': _('user interface encoding'), + 'group': 'ui', 'sitewide': True, + }), + ('language', + {'type' : 'string', + 'default': 'en', + 'vocabulary': Method('available_languages'), + 'help': _('language of the user interface'), + 'group': 'ui', + }), + ('date-format', + {'type' : 'string', + 'default': '%Y/%m/%d', + 'help': _('how to format date in the ui ("man strftime" for format description)'), + 'group': 'ui', + }), + ('datetime-format', + {'type' : 'string', + 'default': '%Y/%m/%d %H:%M', + 'help': _('how to format date and time in the ui ("man strftime" for format description)'), + 'group': 'ui', + }), + ('time-format', + {'type' : 'string', + 'default': '%H:%M', + 'help': _('how to format time in the ui ("man strftime" for format description)'), + 'group': 'ui', + }), + ('float-format', + {'type' : 'string', + 'default': '%.3f', + 'help': _('how to format float numbers in the ui'), + 'group': 'ui', + }), + ('default-text-format', + {'type' : 'choice', + 'choices': ('text/plain', 'text/rest', 'text/html'), + 'default': 'text/html', # use fckeditor in the web ui + 'help': _('default text format for rich text fields.'), + 'group': 'ui', + }), + ('short-line-size', + {'type' : 'int', + 'default': 40, + 'help': _('maximum number of characters in short description'), + 'group': 'navigation', + }), + ) + +def register_persistent_options(options): + global PERSISTENT_OPTIONS + PERSISTENT_OPTIONS = merge_options(PERSISTENT_OPTIONS + options) + +CFGTYPE2ETYPE_MAP = { + 'string': 'String', + 'choice': 'String', + 'yn': 'Boolean', + 'int': 'Int', + 'float' : 'Float', + } + +class CubicWebNoAppConfiguration(ConfigurationMixIn): + """base class for cubicweb configuration without a specific instance directory + """ + __metaclass__ = metaconfiguration + # to set in concrete configuration + name = None + # log messages format (see logging module documentation for available keys) + log_format = '%(asctime)s - (%(name)s) %(levelname)s: %(message)s' + # nor remove vobjects based on unused interface + cleanup_interface_sobjects = True + + if os.environ.get('APYCOT_ROOT'): + mode = 'test' + CUBES_DIR = '%(APYCOT_ROOT)s/local/share/cubicweb/cubes/' % os.environ + elif exists(join(CW_SOFTWARE_ROOT, '.hg')): + mode = 'dev' + CUBES_DIR = join(CW_SOFTWARE_ROOT, '../cubes') + else: + mode = 'installed' + CUBES_DIR = '/usr/share/cubicweb/cubes/' + + options = VREGOPTIONS + ( + ('log-threshold', + {'type' : 'string', # XXX use a dedicated type? + 'default': 'ERROR', + 'help': 'server\'s log level', + 'group': 'main', 'inputlevel': 1, + }), + # pyro name server + ('pyro-ns-host', + {'type' : 'string', + 'default': '', + 'help': 'Pyro name server\'s host. If not set, will be detected by a \ +broadcast query', + 'group': 'pyro-name-server', 'inputlevel': 1, + }), + ('pyro-ns-port', + {'type' : 'int', + 'default': None, + 'help': 'Pyro name server\'s listening port. If not set, default \ +port will be used.', + 'group': 'pyro-name-server', 'inputlevel': 1, + }), + ('pyro-ns-group', + {'type' : 'string', + 'default': 'cubicweb', + 'help': 'Pyro name server\'s group where the repository will be \ +registered.', + 'group': 'pyro-name-server', 'inputlevel': 1, + }), + # common configuration options which are potentially required as soon as + # you're using "base" application objects (ie to really server/web + # specific) + ('base-url', + {'type' : 'string', + 'default': None, + 'help': 'web server root url', + 'group': 'main', 'inputlevel': 1, + }), + ('mangle-emails', + {'type' : 'yn', + 'default': False, + 'help': "don't display actual email addresses but mangle them if \ +this option is set to yes", + 'group': 'email', 'inputlevel': 2, + }), + ) + # static and class methods used to get application independant resources ## + + @staticmethod + def cubicweb_version(): + """return installed cubicweb version""" + from logilab.common.changelog import Version + from cubicweb import __pkginfo__ + version = __pkginfo__.numversion + assert len(version) == 3, version + return Version(version) + + @staticmethod + def persistent_options_configuration(): + return Configuration(options=PERSISTENT_OPTIONS) + + @classmethod + def shared_dir(cls): + """return the shared data directory (i.e. directory where standard + library views and data may be found) + """ + if cls.mode in ('dev', 'test') and not os.environ.get('APYCOT_ROOT'): + return join(CW_SOFTWARE_ROOT, 'web') + return join(cls.cubes_dir(), 'shared') + + @classmethod + def i18n_lib_dir(cls): + """return application's i18n directory""" + if cls.mode in ('dev', 'test') and not os.environ.get('APYCOT_ROOT'): + return join(CW_SOFTWARE_ROOT, 'i18n') + return join(cls.shared_dir(), 'i18n') + + @classmethod + def available_cubes(cls): + cubes_dir = cls.cubes_dir() + return sorted(cube for cube in os.listdir(cubes_dir) + if os.path.isdir(os.path.join(cubes_dir, cube)) + and not cube in ('CVS', '.svn', 'shared', '.hg')) + + @classmethod + def cubes_dir(cls): + """return the application cubes directory""" + return env_path('CW_CUBES', cls.CUBES_DIR, 'cubes') + + @classmethod + def cube_dir(cls, cube): + """return the cube directory for the given cube id, + raise ConfigurationError if it doesn't exists + """ + cube_dir = join(cls.cubes_dir(), cube) + if not exists(cube_dir): + raise ConfigurationError('no cube %s in %s' % ( + cube, cls.cubes_dir())) + return cube_dir + + @classmethod + def cube_migration_scripts_dir(cls, cube): + """cube migration scripts directory""" + return join(cls.cube_dir(cube), 'migration') + + @classmethod + def cube_pkginfo(cls, cube): + """return the information module for the given cube""" + cube = CW_MIGRATION_MAP.get(cube, cube) + try: + return getattr(__import__('cubes.%s.__pkginfo__' % cube), cube).__pkginfo__ + except ImportError: + raise ConfigurationError('unable to find packaging information for ' + 'cube %s' % cube) + + @classmethod + def cube_version(cls, cube): + """return the version of the cube located in the given directory + """ + from logilab.common.changelog import Version + version = cls.cube_pkginfo(cube).numversion + assert len(version) == 3, version + return Version(version) + + @classmethod + def cube_dependencies(cls, cube): + """return cubicweb cubes used by the given cube""" + return getattr(cls.cube_pkginfo(cube), '__use__', ()) + + @classmethod + def cube_recommends(cls, cube): + """return cubicweb cubes recommended by the given cube""" + return getattr(cls.cube_pkginfo(cube), '__recommend__', ()) + + @classmethod + def expand_cubes(cls, cubes): + """expand the given list of top level cubes used by adding recursivly + each cube dependencies + """ + cubes = list(cubes) + todo = cubes[:] + while todo: + cube = todo.pop(0) + for depcube in cls.cube_dependencies(cube): + if depcube not in cubes: + depcube = CW_MIGRATION_MAP.get(depcube, depcube) + cubes.append(depcube) + todo.append(depcube) + return cubes + + @classmethod + def reorder_cubes(cls, cubes): + """reorder cubes from the top level cubes to inner dependencies + cubes + """ + from logilab.common.graph import get_cycles + graph = {} + for cube in cubes: + cube = CW_MIGRATION_MAP.get(cube, cube) + deps = cls.cube_dependencies(cube) + \ + cls.cube_recommends(cube) + graph[cube] = set(dep for dep in deps if dep in cubes) + cycles = get_cycles(graph) + if cycles: + cycles = '\n'.join(' -> '.join(cycle) for cycle in cycles) + raise ConfigurationError('cycles in cubes dependencies: %s' + % cycles) + cubes = [] + while graph: + # sorted to get predictable results + for cube, deps in sorted(graph.items()): + if not deps: + cubes.append(cube) + del graph[cube] + for deps in graph.itervalues(): + try: + deps.remove(cube) + except KeyError: + continue + return tuple(reversed(cubes)) + + @classmethod + def cls_adjust_sys_path(cls): + """update python path if necessary""" + try: + templdir = abspath(join(cls.cubes_dir(), '..')) + if not templdir in sys.path: + sys.path.insert(0, templdir) + except ConfigurationError: + return # cube dir doesn't exists + + @classmethod + def load_cwctl_plugins(cls): + from logilab.common.modutils import load_module_from_file + cls.cls_adjust_sys_path() + for ctlfile in ('web/webctl.py', 'etwist/twctl.py', + 'server/serverctl.py', 'hercule.py', + 'devtools/devctl.py', 'goa/goactl.py'): + if exists(join(CW_SOFTWARE_ROOT, ctlfile)): + load_module_from_file(join(CW_SOFTWARE_ROOT, ctlfile)) + cls.info('loaded cubicweb-ctl plugin %s', ctlfile) + templdir = cls.cubes_dir() + for cube in cls.available_cubes(): + pluginfile = join(templdir, cube, 'ecplugin.py') + initfile = join(templdir, cube, '__init__.py') + if exists(pluginfile): + try: + __import__('cubes.%s.ecplugin' % cube) + cls.info('loaded cubicweb-ctl plugin from %s', cube) + except: + cls.exception('while loading plugin %s', pluginfile) + elif exists(initfile): + try: + __import__('cubes.%s' % cube) + except: + cls.exception('while loading cube %s', cube) + else: + cls.warning('no __init__ file in cube %s', cube) + + @classmethod + def init_available_cubes(cls): + """cubes may register some sources (svnfile for instance) in their + __init__ file, so they should be loaded early in the startup process + """ + for cube in cls.available_cubes(): + try: + __import__('cubes.%s' % cube) + except Exception, ex: + cls.warning("can't init cube %s: %s", cube, ex) + + cubicweb_vobject_path = set(['entities']) + cube_vobject_path = set(['entities']) + + @classmethod + def build_vregistry_path(cls, templpath, evobjpath=None, tvobjpath=None): + """given a list of directories, return a list of sub files and + directories that should be loaded by the application objects registry. + + :param evobjpath: + optional list of sub-directories (or files without the .py ext) of + the cubicweb library that should be tested and added to the output list + if they exists. If not give, default to `cubicweb_vobject_path` class + attribute. + :param tvobjpath: + optional list of sub-directories (or files without the .py ext) of + directories given in `templpath` that should be tested and added to + the output list if they exists. If not give, default to + `cube_vobject_path` class attribute. + """ + vregpath = cls.build_vregistry_cubicweb_path(evobjpath) + vregpath += cls.build_vregistry_cube_path(templpath, tvobjpath) + return vregpath + + @classmethod + def build_vregistry_cubicweb_path(cls, evobjpath=None): + vregpath = [] + if evobjpath is None: + evobjpath = cls.cubicweb_vobject_path + for subdir in evobjpath: + path = join(CW_SOFTWARE_ROOT, subdir) + if exists(path): + vregpath.append(path) + return vregpath + + @classmethod + def build_vregistry_cube_path(cls, templpath, tvobjpath=None): + vregpath = [] + if tvobjpath is None: + tvobjpath = cls.cube_vobject_path + for directory in templpath: + for subdir in tvobjpath: + path = join(directory, subdir) + if exists(path): + vregpath.append(path) + elif exists(path + '.py'): + vregpath.append(path + '.py') + return vregpath + + def __init__(self): + ConfigurationMixIn.__init__(self) + self.adjust_sys_path() + self.load_defaults() + self.translations = {} + + def adjust_sys_path(self): + self.cls_adjust_sys_path() + + def init_log(self, logthreshold=None, debug=False, + logfile=None, syslog=False): + """init the log service""" + if os.environ.get('APYCOT_ROOT'): + logthreshold = logging.CRITICAL + # redirect logs to stdout to avoid apycot output parsing failure + handler = logging.StreamHandler(sys.stdout) + else: + if debug: + if logthreshold is None: + logthreshold = logging.DEBUG # LLDEBUG + handler = logging.StreamHandler() + elif logfile is None: + if syslog: + from logging import handlers + handler = handlers.SysLogHandler() + else: + handler = logging.StreamHandler() + else: + try: + handler = logging.FileHandler(logfile) + except IOError: + handler = logging.StreamHandler() + if logthreshold is None: + thresholdname = self['log-threshold'] + logthreshold = getattr(logging, THRESHOLD_MAP.get(thresholdname, + thresholdname)) + # configure the root logger + logger = logging.getLogger() + logger.setLevel(logthreshold) + # only addHandler and removeHandler method while I would like a + # setHandler method, so do it this way :$ + logger.handlers = [handler] + isatty = hasattr(sys.__stdout__, 'isatty') and sys.__stdout__.isatty() + if debug and isatty: + from logilab.common.logging_ext import ColorFormatter + fmt = ColorFormatter(self.log_format, '%Y-%m-%d %H:%M:%S') + def col_fact(record): + if 'XXX' in record.message: + return 'cyan' + if 'kick' in record.message: + return 'red' + fmt.colorfilters.append(col_fact) + else: + fmt = logging.Formatter(self.log_format, '%Y-%m-%d %H:%M:%S') + logger.handlers[0].setFormatter(fmt) + # configure simpleTal logger + logging.getLogger('simpleTAL').setLevel(logging.ERROR) + + def vregistry_path(self): + """return a list of files or directories where the registry will look + for application objects. By default return nothing in NoApp config. + """ + return [] + + def eproperty_definitions(self): + cfg = self.persistent_options_configuration() + for section, options in cfg.options_by_section(): + section = section.lower() + for optname, optdict, value in options: + key = '%s.%s' % (section, optname) + type, vocab = self.map_option(optdict) + default = cfg.option_default(optname, optdict) + pdef = {'type': type, 'vocabulary': vocab, 'default': default, + 'help': optdict['help'], + 'sitewide': optdict.get('sitewide', False)} + yield key, pdef + + def map_option(self, optdict): + try: + vocab = optdict['choices'] + except KeyError: + vocab = optdict.get('vocabulary') + if isinstance(vocab, Method): + vocab = getattr(self, vocab.method, ()) + return CFGTYPE2ETYPE_MAP[optdict['type']], vocab + + +class CubicWebConfiguration(CubicWebNoAppConfiguration): + """base class for cubicweb server and web configurations""" + + if CubicWebNoAppConfiguration.mode == 'test': + root = os.environ['APYCOT_ROOT'] + REGISTRY_DIR = '%s/etc/cubicweb.d/' % root + INSTANCE_DATA_DIR = REGISTRY_DIR + RUNTIME_DIR = '/tmp/' + MIGRATION_DIR = '%s/local/share/cubicweb/migration/' % root + if not exists(REGISTRY_DIR): + os.makedirs(REGISTRY_DIR) + elif CubicWebNoAppConfiguration.mode == 'dev': + REGISTRY_DIR = expanduser('~/etc/cubicweb.d/') + INSTANCE_DATA_DIR = REGISTRY_DIR + RUNTIME_DIR = '/tmp/' + MIGRATION_DIR = join(CW_SOFTWARE_ROOT, 'misc', 'migration') + else: #mode = 'installed' + REGISTRY_DIR = '/etc/cubicweb.d/' + INSTANCE_DATA_DIR = '/var/lib/cubicweb/instances/' + RUNTIME_DIR = '/var/run/cubicweb/' + MIGRATION_DIR = '/usr/share/cubicweb/migration/' + + # for some commands (creation...) we don't want to initialize gettext + set_language = True + # set this to true to avoid false error message while creating an application + creating = False + + options = CubicWebNoAppConfiguration.options + ( + ('log-file', + {'type' : 'string', + 'default': Method('default_log_file'), + 'help': 'file where output logs should be written', + 'group': 'main', 'inputlevel': 2, + }), + # email configuration + ('smtp-host', + {'type' : 'string', + 'default': 'mail', + 'help': 'hostname of the SMTP mail server', + 'group': 'email', 'inputlevel': 1, + }), + ('smtp-port', + {'type' : 'int', + 'default': 25, + 'help': 'listening port of the SMTP mail server', + 'group': 'email', 'inputlevel': 1, + }), + ('sender-name', + {'type' : 'string', + 'default': Method('default_application_id'), + 'help': 'name used as HELO name for outgoing emails from the \ +repository.', + 'group': 'email', 'inputlevel': 2, + }), + ('sender-addr', + {'type' : 'string', + 'default': 'devel@logilab.fr', + 'help': 'email address used as HELO address for outgoing emails from \ +the repository', + 'group': 'email', 'inputlevel': 1, + }), + ) + + @classmethod + def runtime_dir(cls): + """run time directory for pid file...""" + return env_path('CW_RUNTIME', cls.RUNTIME_DIR, 'run time') + + @classmethod + def registry_dir(cls): + """return the control directory""" + return env_path('CW_REGISTRY', cls.REGISTRY_DIR, 'registry') + + @classmethod + def instance_data_dir(cls): + """return the instance data directory""" + return env_path('CW_INSTANCE_DATA', cls.INSTANCE_DATA_DIR, + 'additional data') + + @classmethod + def migration_scripts_dir(cls): + """cubicweb migration scripts directory""" + return env_path('CW_MIGRATION', cls.MIGRATION_DIR, 'migration') + + @classmethod + def config_for(cls, appid, config=None): + """return a configuration instance for the given application identifier + """ + config = config or guess_configuration(cls.application_home(appid)) + configcls = configuration_cls(config) + return configcls(appid) + + @classmethod + def possible_configurations(cls, appid): + """return the name of possible configurations for the given + application id + """ + home = cls.application_home(appid) + return possible_configurations(home) + + @classmethod + def application_home(cls, appid): + """return the home directory of the application with the given + application id + """ + home = join(cls.registry_dir(), appid) + if not exists(home): + raise ConfigurationError('no such application %s (check it exists with "cubicweb-ctl list")' % appid) + return home + + MODES = ('common', 'repository', 'Any', 'web') + MCOMPAT = {'all-in-one': MODES, + 'repository': ('common', 'repository', 'Any'), + 'twisted' : ('common', 'web'),} + @classmethod + def accept_mode(cls, mode): + #assert mode in cls.MODES, mode + return mode in cls.MCOMPAT[cls.name] + + # default configuration methods ########################################### + + def default_application_id(self): + """return the application identifier, useful for option which need this + as default value + """ + return self.appid + + def default_log_file(self): + """return default path to the log file of the application'server""" + if self.mode == 'dev': + basepath = '/tmp/%s-%s' % (basename(self.appid), self.name) + path = basepath + '.log' + i = 1 + while exists(path) and i < 100: # arbitrary limit to avoid infinite loop + try: + file(path, 'a') + break + except IOError: + path = '%s-%s.log' % (basepath, i) + i += 1 + return path + return '/var/log/cubicweb/%s-%s.log' % (self.appid, self.name) + + def default_pid_file(self): + """return default path to the pid file of the application'server""" + return join(self.runtime_dir(), '%s-%s.pid' % (self.appid, self.name)) + + # instance methods used to get application specific resources ############# + + def __init__(self, appid): + self.appid = appid + CubicWebNoAppConfiguration.__init__(self) + self._cubes = None + self._site_loaded = set() + self.load_file_configuration(self.main_config_file()) + + def adjust_sys_path(self): + CubicWebNoAppConfiguration.adjust_sys_path(self) + # adding apphome to python path is not usually necessary in production + # environments, but necessary for tests + if self.apphome and not self.apphome in sys.path: + sys.path.insert(0, self.apphome) + + @property + def apphome(self): + return join(self.registry_dir(), self.appid) + + @property + def appdatahome(self): + return join(self.instance_data_dir(), self.appid) + + def init_cubes(self, cubes): + assert self._cubes is None + self._cubes = self.reorder_cubes(cubes) + # load cubes'__init__.py file first + for cube in cubes: + __import__('cubes.%s' % cube) + self.load_site_cubicweb() + # reload config file in cases options are defined in cubes __init__ + # or site_cubicweb files + self.load_file_configuration(self.main_config_file()) + # configuration initialization hook + self.load_configuration() + + def cubes(self): + """return the list of cubes used by this instance + + result is ordered from the top level cubes to inner dependencies + cubes + """ + assert self._cubes is not None + return self._cubes + + def cubes_path(self): + """return the list of path to cubes used by this instance, from outer + most to inner most cubes + """ + return [self.cube_dir(p) for p in self.cubes()] + + def add_cubes(self, cubes): + """add given cubes to the list of used cubes""" + if not isinstance(cubes, list): + cubes = list(cubes) + self._cubes = self.reorder_cubes(list(self._cubes) + cubes) + + def main_config_file(self): + """return application's control configuration file""" + return join(self.apphome, '%s.conf' % self.name) + + def save(self): + """write down current configuration""" + self.generate_config(open(self.main_config_file(), 'w')) + + @cached + def instance_md5_version(self): + import md5 + infos = [] + for pkg in self.cubes(): + version = self.cube_version(pkg) + infos.append('%s-%s' % (pkg, version)) + return md5.new(';'.join(infos)).hexdigest() + + def load_site_cubicweb(self): + """load (web?) application's specific site_cubicweb file""" + for path in reversed([self.apphome] + self.cubes_path()): + sitefile = join(path, 'site_cubicweb.py') + if exists(sitefile) and not sitefile in self._site_loaded: + self._load_site_cubicweb(sitefile) + self._site_loaded.add(sitefile) + else: + sitefile = join(path, 'site_erudi.py') + if exists(sitefile) and not sitefile in self._site_loaded: + self._load_site_cubicweb(sitefile) + self._site_loaded.add(sitefile) + self.warning('site_erudi.py is deprecated, should be renamed to site_cubicweb.py') + + def _load_site_cubicweb(self, sitefile): + context = {} + execfile(sitefile, context, context) + self.info('%s loaded', sitefile) + # cube specific options + if context.get('options'): + self.register_options(context['options']) + self.load_defaults() + + def load_configuration(self): + """load application's configuration files""" + super(CubicWebConfiguration, self).load_configuration() + if self.apphome and self.set_language: + # init gettext + self._set_language() + + def init_log(self, logthreshold=None, debug=False, force=False): + """init the log service""" + if not force and hasattr(self, '_logging_initialized'): + return + self._logging_initialized = True + CubicWebNoAppConfiguration.init_log(self, logthreshold, debug, + logfile=self.get('log-file')) + # read a config file if it exists + logconfig = join(self.apphome, 'logging.conf') + if exists(logconfig): + logging.fileConfig(logconfig) + + def available_languages(self, *args): + """return available translation for an application, by looking for + compiled catalog + + take *args to be usable as a vocabulary method + """ + from glob import glob + yield 'en' # ensure 'en' is yielded even if no .mo found + for path in glob(join(self.apphome, 'i18n', + '*', 'LC_MESSAGES', 'cubicweb.mo')): + lang = path.split(os.sep)[-3] + if lang != 'en': + yield lang + + def _set_language(self): + """set language for gettext""" + from gettext import translation + path = join(self.apphome, 'i18n') + for language in self.available_languages(): + self.info("loading language %s", language) + try: + tr = translation('cubicweb', path, languages=[language]) + self.translations[language] = tr.ugettext + except (ImportError, AttributeError, IOError): + self.exception('localisation support error for language %s', + language) + + def vregistry_path(self): + """return a list of files or directories where the registry will look + for application objects + """ + templpath = list(reversed(self.cubes_path())) + if self.apphome: # may be unset in tests + templpath.append(self.apphome) + return self.build_vregistry_path(templpath) + + def set_sources_mode(self, sources): + if not 'all' in sources: + print 'warning: ignoring specified sources, requires a repository '\ + 'configuration' + + def migration_handler(self): + """return a migration handler instance""" + from cubicweb.common.migration import MigrationHelper + return MigrationHelper(self, verbosity=self.verbosity) + + def i18ncompile(self, langs=None): + from cubicweb.common import i18n + if langs is None: + langs = self.available_languages() + i18ndir = join(self.apphome, 'i18n') + if not exists(i18ndir): + create_dir(i18ndir) + sourcedirs = [join(path, 'i18n') for path in self.cubes_path()] + sourcedirs.append(self.i18n_lib_dir()) + return i18n.compile_i18n_catalogs(sourcedirs, i18ndir, langs) + + +# alias to get a configuration instance from an application id +application_configuration = CubicWebConfiguration.config_for + +# map logilab.common.logger thresholds to logging thresholds +THRESHOLD_MAP = {'LOG_DEBUG': 'DEBUG', + 'LOG_INFO': 'INFO', + 'LOG_NOTICE': 'INFO', + 'LOG_WARN': 'WARNING', + 'LOG_ERR': 'ERROR', + 'LOG_CRIT': 'CRITICAL', + } + +from cubicweb import set_log_methods +set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration'))