# -*- coding: utf-8 -*- """ Random functions that don't fit elsewhere """ from __future__ import division import os import time from turbogears import config, url import logging import codecs import cherrypy import turbogears from turbogears.i18n.tg_gettext import get_locale_dir try : from pygments import highlight from pygments.lexers import DiffLexer from pygments.formatters import HtmlFormatter pygmentsExist = True except ImportError : pygmentsExist = False log = logging.getLogger(__name__) def load_config(configfile=None): """ Load the appropriate configuration so we can get at the values """ from os import path from turbogears import update_config if not configfile: configfile = 'prod.cfg' if not path.isfile(configfile): configfile = 'dev.cfg' update_config(configfile=configfile, modulename='transifex.config') ## Display a given message as a heading def header(text, width=79): return "%s\n %s\n%s" % ('=' * width, text, '=' * width) def get_user_email(identity=None, user=None): """ Return the email address of the current identity """ if identity: if hasattr(identity.current.user, 'user'): return identity.current.user.user['email'] elif hasattr(identity.current.user, 'email_address'): return identity.current.user.email_address else: return None if user: return user.email_address return def get_user_info(user=None): """ Return the Full Name of the current identity or user """ from transifex.util import get_user_email from turbogears import identity if user: (name, email) = (user.display_name, user.email_address) else: (name, email) = (identity.current.user.display_name, get_user_email(identity)) ret = '%s%s%s' % (name, ' <%s>' % email or '', ' (via %s@fedoraproject.org)' % identity.current.user.user_name or '') return ret def get_repositories(): from transifex.model import Repository return Repository.select(Repository.q.disabled == False, orderBy=Repository.q.description) def get_modules(): from transifex.model import Module return Module.select(Module.q.disabled == False, orderBy=Module.q.description) def get_branches(): from transifex.model import Branch return Branch.select(orderBy=Branch.q.description) def msgfmt_check(file): """ Runs a `msgfmt -c` on a file (file object). Raises a ValueError in case the file has errors. """ if run_command({'args': ["msgfmt", "-c", "-"], 'stdin': file}): raise ValueError, "You uploaded a PO file which doesn't pass the" \ " check for correctness (msgfmt -c). Please run" \ " this command on your system to see the errors." def run_commands(coms): """ Run a set of system commands, aborting when one of them has failed. """ for com in coms: com_ret = run_command(com) if com_ret: return 1 else: return 0 def run_command(command=None): """ Run a system command. Argument is either a simple list which is run, or a dict with the command in 'args' and the extra parameters thrown as they are at subprocess.call(). """ import subprocess command_dict = {} if isinstance(command, list): pass elif isinstance(command, dict): if not command.has_key('args'): raise ValueError, 'List, or dict(args, **kw) accepted' command2 = command command = command.pop('args') command_dict = command2 else: raise ValueError, 'List, or dict(args, **kw) accepted' log.debug("Executing: `%s`" % ' '.join(command)) if len(command_dict) > 0: log.debug("Args: %s" % repr(command_dict)) #FIXME: How (and where) can we output/log the output/errors as well? # http://docs.python.org/lib/node529.html try: ret = subprocess.call(command, **command_dict) if ret < 0: log.error("Command %s was terminated by signal %s." % (command, ret)) elif ret > 0: log.error("Command %s returned %s." % (command, ret)) else: #log.debug("Command succeeded") pass return ret except OSError, e: log.error("Command %s failed: %s", (command, e)) return 1 def fit_text(text, length, ellipsis=u"…"): """ Fits a chunk of text in a particular width by putting an elleipsis in its middle. For example, foobarbazquxquux could become foo...ux """ from math import ceil if len(text) <= length: return text if length < len(ellipsis): raise Exception leftindex = int(ceil((length - len(ellipsis)) / 2)) rightindex = len(text)-int((length - len(ellipsis)) / 2) return text[:leftindex] + ellipsis + text[rightindex:] def wrap(text, width): """ A word-wrap function that preserves existing line breaks and most spaces in the text. Expects that existing line breaks are posix newlines (\n). Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 """ return reduce(lambda line, word, width=width: '%s%s%s' % (line, ' \n'[(len(line)-line.rfind('\n')-1 + len(word.split('\n',1)[0] ) >= width)], word), text.split(' ') ) class SubmitEntries: """ Wrapper for showing the last 10 file submissions for a module or a user """ entries = None datagrid = None def userinfo(self, m): """Return a user's name and link to his page.""" from cgi import escape from kid import XML import re p = re.compile('^(.*) <(.*)> \(via (.*)\)$') match = p.match(m.userinfo) if match: return XML('%s' % (escape(m.userinfo), match.group(1))) else: return XML(escape(m.userinfo)) def moduleinfo(self, m): """Return a module's name and link to its page.""" from cgi import escape from kid import XML return XML('%(mdesc)s (%(branch)s)' % { 'mdesc': escape(m.module.description or m.module.name), 'branch': m.branch.name}) def branchinfo(self, m): return m.branch.name def summaryinfo(self, m): from cgi import escape from kid import XML return XML('
'
                   + escape(m.summary or '')
                   + '
') def format_action(self, m): from kid import XML, Element try: return Element('img', title=m.type, alt=m.type, src=url('/static/images/icons/%s' % { 'submit': lambda: 'document-save-as16.png', 'preview': lambda: 'system-search16.png', }[m.type]()) ) except KeyError: return '' def ModuleEntries(self, module=None, branch=None, limit=10): from sqlobject.sqlbuilder import AND from turbogears.widgets import DataGrid from transifex.model import ActionLog self.entries = ActionLog.select(AND(ActionLog.q.moduleID == module.id, ActionLog.q.branchID == branch.id, ActionLog.q.type == 'submit'), orderBy=ActionLog.q.timestamp ).reversed()[:limit] self.datagrid = DataGrid(fields=[ #FIXME: Cache user info, username etc. (_('User'), self.userinfo), #(_('Branch'), self.branchinfo), (_('File(s)'), 'filenames'), (_('Summary'), self.summaryinfo), DataGrid.Column(_('Timestamp'), 'timestamp', 'Timestamp', options=dict(sortable=True)), ]) def UserEntries(self, user=None, limit=10): from sqlobject.sqlbuilder import AND from turbogears.widgets import DataGrid from transifex.model import ActionLog self.entries = ActionLog.select(AND(ActionLog.q.userID == user.id, ActionLog.q.type == 'submit'), orderBy=ActionLog.q.timestamp ).reversed()[:limit] self.datagrid = DataGrid(fields=[ (_('Module'), self.moduleinfo), (_('File(s)'), 'filenames'), (_('Summary'), self.summaryinfo), DataGrid.Column(_('Timestamp'), 'timestamp', 'Timestamp', options=dict(sortable=True)), ]) def AllEntries(self, limit=10): from turbogears.widgets import DataGrid from transifex.model import ActionLog self.entries = ActionLog.select(orderBy=ActionLog.q.timestamp ).reversed()[:limit] self.datagrid = DataGrid(fields=[ DataGrid.Column('type', self.format_action, _('Act'), options=dict(sortable=True)), DataGrid.Column('userinfo', self.userinfo, _('User'), options=dict(sortable=True)), DataGrid.Column('module', self.moduleinfo, _('Module'),), DataGrid.Column('filenames', 'filenames', _('File(s)'), options=dict(sortable=True)), DataGrid.Column('Changelog', self.summaryinfo, _('Changelog'), options=dict(sortable=True)), DataGrid.Column('timestamp', 'timestamp', _('Timestamp'), options=dict(sortable=True, reverse_order=True)), ]) def __init__(self, module=None, branch=None, user=None, limit=10): #FIXME: Quite a hacky way to accomplish stuff... if module: self.ModuleEntries(module=module, branch=branch, limit=limit) elif user: self.UserEntries(user=user, limit=limit) else: self.AllEntries(limit=limit) def timesince(d, now=None): """ Takes two datetime objects and returns the time between then and now as a nicely formatted string, e.g "10 minutes" Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since Taken from django """ import datetime, math, time chunks = ( (60 * 60 * 24 * 365, lambda n: ((n == 1) and _('year') or _('years'))), (60 * 60 * 24 * 30, lambda n: ((n == 1) and _('month') or _('months'))), (60 * 60 * 24 * 7, lambda n : ((n == 1) and _('week') or _('weeks'))), (60 * 60 * 24, lambda n : ((n == 1) and _('day') or _('days'))), (60 * 60, lambda n: ((n == 1) and _('hour') or _('hours'))), (60, lambda n: ((n == 1) and _('minute') or _('minutes'))) ) # Convert datetime.date to datetime.datetime for comparison if d.__class__ is not datetime.datetime: d = datetime.datetime(d.year, d.month, d.day) if now: t = now.timetuple() else: t = time.localtime() if d.tzinfo: tz = None # tz = LocalTimezone(d) else: tz = None now = datetime.datetime(t[0], t[1], t[2], t[3], t[4], t[5], tzinfo=tz) # ignore microsecond part of 'd' since we removed it from 'now' delta = now - (d - datetime.timedelta(0, 0, d.microsecond)) since = delta.days * 24 * 60 * 60 + delta.seconds for i, (seconds, name) in enumerate(chunks): count = since // seconds if count != 0: break s = _('%(number)d %(type)s') % {'number': count, 'type': name(count)} if i + 1 < len(chunks): # Now get the second item seconds2, name2 = chunks[i + 1] count2 = (since - (seconds * count)) // seconds2 if count2 != 0: s += _(', %(number)d %(type)s') % {'number': count2, 'type': name2(count2)} return s # Generate diffs def get_diffs(text1='', text2='', diffStr1='', diffStr2=''): """ Returns the diff from two chunks of text. If third argument is used, it should be a path to a file, and its creation time will be used as the diff time for the first chunk. """ from difflib import unified_diff from cgi import escape diffset = unified_diff( text1.splitlines(1), text2.splitlines(1), diffStr1, diffStr2) diff = ''.join(diffset) if pygmentsExist: lexer = DiffLexer() formatter = HtmlFormatter(linenos=False, style="trac", cssclass="highlight sane_size") diffHL = highlight(diff, lexer, formatter) else: diffHL = '
'+escape(diff)+'
' return (diff, diffHL) def get_diff_strs(fileName, filePath): from os import path timeformat = "%d %b %Y %H:%M:%S %Z" if filePath and path.isfile(filePath): time1 = '%s\t%s' % (fileName, time.strftime(timeformat, time.localtime( os.stat(filePath).st_mtime))) time2 = '%s\t%s' % (fileName, time.strftime(timeformat, time.localtime())) else: time1 = '%s\t%s' % (fileName, time.strftime(timeformat, time.localtime())) time2 = '%s\t%s' % (fileName, time.strftime(timeformat, time.localtime())) return (time1, time2) # i18n support def available_languages(): """Return available languages for a given domain.""" available_languages = [] localedir = get_locale_dir() try: linguas = codecs.open(os.path.join(localedir, 'LINGUAS'), 'r') for lang in linguas.readlines(): lang = lang.strip() if lang and not lang.startswith('#'): available_languages.append(lang) except IOError, e: log.warning(_('The LINGUAS file could not be opened: %s') % e) return available_languages # i18n support def get_locale(locale=None): if locale: return locale if cherrypy.request.simple_cookie.has_key('lang'): turbogears.i18n.set_session_locale(cherrypy.request.simple_cookie['lang'].value) return cherrypy.request.simple_cookie['lang'].value else: if turbogears.i18n.utils._get_locale() not in available_languages(): turbogears.i18n.set_session_locale('en') return 'en' else: return turbogears.i18n.utils._get_locale()