| Index: utils.py | 
| diff --git a/utils.py b/utils.py | 
| index e1d16f5d3eb84c4321a68f8f595e3d31606d365a..b689666218034c1cb1207d17b7f9c94618b33ba9 100644 | 
| --- a/utils.py | 
| +++ b/utils.py | 
| @@ -1,9 +1,18 @@ | 
| + | 
| # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
| # Use of this source code is governed by a BSD-style license that can be | 
| # found in the LICENSE file. | 
|  | 
| """Utils.""" | 
|  | 
| +import re | 
| +import time | 
| +import random | 
| +import logging | 
| +import sys | 
| +import string | 
| +import json | 
| + | 
| from google.appengine.api import users | 
|  | 
|  | 
| @@ -35,3 +44,229 @@ def require_user(func): | 
| else: | 
| return func(self, *args, **kwargs) | 
| return decorated | 
| + | 
| + | 
| +############ | 
| +# Decorators | 
| +############ | 
| + | 
| +def render(template_filename, jinja_environment): | 
| +  """Use a template to render results.  The wrapped function is expected to | 
| +  return a dict.""" | 
| +  def _render(fn): | 
| +    def wrapper(self, *args, **kwargs): | 
| +      results = fn(self, *args, **kwargs) | 
| +      template = jinja_environment.get_template(template_filename) | 
| +      self.response.out.write(template.render(results)) | 
| +    return wrapper | 
| +  return _render | 
| + | 
| + | 
| +def render_iff_new_flag_set(template_filename, jinja_environment): | 
| +  """Use the given template if and only if the 'new' flag is set by: | 
| +  * The presence of the 'new' cookie. | 
| +  * 'new' is passed in as an url parameter.""" | 
| +  def _render(fn): | 
| +    def wrapper(self, *args, **kwargs): | 
| +      new = self.request.get('new') or self.request.cookies.get('new') | 
| +      kwargs.update({'new': new}) | 
| +      results = fn(self, *args, **kwargs) | 
| +      if new: | 
| +        template = jinja_environment.get_template(template_filename) | 
| +        try: | 
| +          self.response.out.write(template.render(results)) | 
| +        except Exception as e: | 
| +          logging.error('Caught exception while calling %s with template %s' % | 
| +                        (self.__class__.__name__, template_filename)) | 
| +          raise e, None, sys.exc_info()[2] | 
| +      else: | 
| +        # Just treat the results as a large string blob. | 
| +        self.response.out.write(results) | 
| +    return wrapper | 
| +  return _render | 
| + | 
| + | 
| +def render_json(fn): | 
| +  """The function is expected to return a dict, and we want to render json.""" | 
| +  def wrapper(self, *args, **kwargs): | 
| +    results = fn(self, *args, **kwargs) | 
| +    self.response.out.write(json.dumps(results)) | 
| +  return wrapper | 
| + | 
| + | 
| +def maybe_render_json(template_filename, jinja_environment): | 
| +  """If the variable 'json' exists in the request, return a json object. | 
| +  Otherwise render the page using the template""" | 
| +  def _render(fn): | 
| +    def wrapper(self, *args, **kwargs): | 
| +      results = fn(self, *args, **kwargs) | 
| +      if self.request.get('json'): | 
| +        self.response.out.write(json.dumps(results)) | 
| +      else: | 
| +        template = jinja_environment.get_template(template_filename) | 
| +        self.response.out.write(template.render(results)) | 
| +    return wrapper | 
| +  return _render | 
| + | 
| + | 
| +def login_required(fn): | 
| +  """Redirect user to a login page.""" | 
| +  def wrapper(self, *args, **kwargs): | 
| +    user = users.get_current_user() | 
| +    if not user: | 
| +      self.redirect(users.create_login_url(self.request.uri)) | 
| +      return | 
| +    else: | 
| +      return fn(self, *args, **kwargs) | 
| +  return wrapper | 
| + | 
| + | 
| +def google_login_required(fn): | 
| +  """Return 403 unless the user is logged in from a @google.com domain.""" | 
| +  def wrapper(self, *args, **kwargs): | 
| +    user = users.get_current_user() | 
| +    if not user: | 
| +      self.redirect(users.create_login_url(self.request.uri)) | 
| +      return | 
| +    email_match = re.match('^(.*)@(.*)$', user.email()) | 
| +    if email_match: | 
| +      _, domain = email_match.groups() | 
| +      if domain == 'google.com': | 
| +        return fn(self, *args, **kwargs) | 
| +    self.error(403)  # Unrecognized email or unauthroized domain. | 
| +    self.response.out.write('unauthroized email %s' % user.user_id()) | 
| +  return wrapper | 
| + | 
| + | 
| +def admin_required(fn): | 
| +  """Return 403 unless an admin is logged in.""" | 
| +  def wrapper(self, *args, **kwargs): | 
| +    user = users.get_current_user() | 
| +    if not user: | 
| +      self.redirect(users.create_login_url(self.request.uri)) | 
| +      return | 
| +    elif not users.is_current_user_admin(): | 
| +      self.error(403) | 
| +      return | 
| +    else: | 
| +      return fn(self, *args, **kwargs) | 
| +  return wrapper | 
| + | 
| + | 
| +def expect_request_param(*request_args): | 
| +  """Strips out the expected args from a request and feeds it into the function | 
| +  as the arguments.  Optionally, typecast the argument from a string into a | 
| +  different class.  Examples include: | 
| +  name                  (Get the request object called "name") | 
| +  time as timestamp     (Get "time", pass it in as "timestamp") | 
| +  """ | 
| +  def _decorator(fn): | 
| +    def wrapper(self, *args, **kwargs): | 
| +      request_kwargs = {} | 
| +      for arg in request_args: | 
| +        # TODO(hinoka): Optional typecasting? | 
| +        arg_match = re.match(r'^(\((\w+)\))?\s*(\w+)( as (\w+))?$', arg) | 
| +        if arg_match: | 
| +          _, _, name, _, target_name = arg_match.groups() | 
| +          if not target_name: | 
| +            target_name = name | 
| +          request_item = self.request.get(name) | 
| +          request_kwargs[target_name] = request_item | 
| +        else: | 
| +          raise Exception('Incorrect format %s' % arg) | 
| +      kwargs.update(request_kwargs) | 
| +      return fn(self, *args, **kwargs) | 
| +    return wrapper | 
| +  return _decorator | 
| + | 
| + | 
| +############### | 
| +# Jinja filters | 
| +############### | 
| + | 
| +def delta_time(delta): | 
| +  hours = int(delta/60/60) | 
| +  minutes = int((delta - hours * 3600)/60) | 
| +  seconds = int(delta - (hours * 3600) - (minutes * 60)) | 
| +  result = '' | 
| +  if hours > 1: | 
| +    result += '%d hrs, ' % hours | 
| +  elif hours: | 
| +    result += '%d hr, ' % hours | 
| +  if minutes > 1: | 
| +    result += '%d mins ' % minutes | 
| +  elif minutes: | 
| +    result += '%d min ' % minutes | 
| +  if not hours: | 
| +    if seconds > 1 or seconds == 0: | 
| +      result += '%d secs.' % seconds | 
| +    else: | 
| +      result += '%d sec.' % seconds | 
| +  return result | 
| + | 
| + | 
| +def time_since(timestamp): | 
| +  delta = time.time() - timestamp | 
| +  return delta_time(delta) | 
| + | 
| + | 
| +def nl2br(value): | 
| +  return value.replace('\n','<br>\n') | 
| + | 
| + | 
| +def rot13_email(value): | 
| +  nonce = ''.join(random.choice( | 
| +      string.ascii_uppercase + string.digits) for x in range(6)) | 
| +  rep = ('<span id="obf-%s"><script>document.getElementById("obf-%s").' | 
| +         'innerHTML="<n uers=\\"znvygb:%s\\" gnetrg=\\"_oynax\\">%s</n>".' | 
| +         'replace(/[a-zA-Z]/g,function(c){return String.fromCharCode((' | 
| +         'c<="Z"?90:122)>=(c=c.charCodeAt(0)+13)?c:c-26);});</script>' | 
| +         '<noscript><span style="unicode-bidi:bidi-override;direction:rtl;"' | 
| +         '>%s</span></noscript></span>') | 
| +  return rep % (nonce, nonce, value.encode('rot13'), | 
| +      value.encode('rot13'), value[::-1]) | 
| + | 
| + | 
| +def _blockquote(value): | 
| +  """Wrap blockquote levels recursively.""" | 
| +  new_value = '' | 
| +  blockquote = False | 
| +  for line in value.splitlines(): | 
| +    if blockquote: | 
| +      if line.startswith('>'): | 
| +        new_value += '%s\n' % line[1:].strip() | 
| +      else: | 
| +        blockquote = False | 
| +        new_value += '</blockquote>%s\n' % line | 
| +    else: | 
| +      if line.startswith('>'): | 
| +        blockquote = True | 
| +        new_value += '<blockquote>%s\n' % line[1:].strip() | 
| +      else: | 
| +        new_value += '%s\n' % line | 
| +  if blockquote: | 
| +    new_value += '</blockquote>' | 
| +  if re.search(r'^>', new_value, re.M): | 
| +    return _blockquote(new_value) | 
| +  else: | 
| +    return new_value | 
| + | 
| + | 
| +def _resolve_crbug(match): | 
| +  results = [] | 
| +  bugs = match.group(1).split(',') | 
| +  for bug in bugs: | 
| +    results.append('<a href="http://crbug.com/%s">%s</a>' % (bug, bug)) | 
| +  return 'BUG=%s' % ','.join(results) | 
| + | 
| + | 
| +def cl_comment(value): | 
| +  """Add links to https:// addresses, BUG=####, and trim excessive newlines.""" | 
| +  value = re.sub(r'(https?://.*)', r'<a href="\1">\1</a>', value) | 
| +  value = re.sub(r'BUG=([\d,]+)', _resolve_crbug, value) | 
| +  # Add blockquotes. | 
| +  value = _blockquote(value) | 
| +  value = re.sub(r'\n', r'<br>', value) | 
| +  # Obfuscure email addresses with rot13 encoding. | 
| +  value = re.sub(r'(\w+@[\w.]+)', lambda m: rot13_email(m.group(1)), value) | 
| +  return value | 
|  |