Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: status.py

Issue 84943003: chromium-status: automatically linkify usernames/status messages (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/chromium-status
Patch Set: address Vadim's feedback in status.py Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | stylesheets/style.css » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # coding=utf-8 1 # coding=utf-8
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """Status management pages.""" 6 """Status management pages."""
7 7
8 import datetime 8 import datetime
9 import json 9 import json
10 import re 10 import re
11 11
12 from google.appengine.api import memcache 12 from google.appengine.api import memcache
13 from google.appengine.ext import db 13 from google.appengine.ext import db
14 14
15 from base_page import BasePage 15 from base_page import BasePage
16 import utils 16 import utils
17 17
18 18
19 ALLOWED_ORIGINS = [ 19 ALLOWED_ORIGINS = [
20 'https://gerrit-int.chromium.org', 20 'https://gerrit-int.chromium.org',
21 'https://gerrit.chromium.org', 21 'https://gerrit.chromium.org',
22 'https://chrome-internal-review.googlesource.com', 22 'https://chrome-internal-review.googlesource.com',
23 'https://chromium-review.googlesource.com', 23 'https://chromium-review.googlesource.com',
24 ] 24 ]
25 25
26 26
27 class TextFragment(object):
28 """Simple object to hold text that might be linked"""
29
30 def __init__(self, text, target=None, is_email=False):
31 self.text = text
32 self.target = target
33 self.is_email = is_email
34
35 def __repr__(self):
36 return 'TextFragment({%s->%s})' % (self.text, self.target)
37
38
39 class LinkableText(object):
40 """Turns arbitrary text into a set of links"""
41
42 GERRIT_URLS = {
43 'chrome': 'https://chrome-internal-review.googlesource.com',
44 'chromium': 'https://chromium-review.googlesource.com',
45 }
46
47 WATERFALL_URLS = {
48 'chromeos': 'http://chromegw/i/chromeos',
49 'chromiumos': 'http://build.chromium.org/p/chromiumos',
50 }
51
52 # Automatically linkify known strings for the user.
53 _CONVERTS = []
54
55 @classmethod
56 def register_converter(cls, regex, target, pretty, is_email, flags=re.I):
57 """Register a new conversion for creating links from text"""
58 cls._CONVERTS.append(
59 (re.compile(regex, flags=flags), target, pretty, is_email))
60
61 @classmethod
62 def bootstrap(cls, _app_name):
63 """Add conversions (possibly specific to |app_name| instance)"""
64 # Convert CrOS bug links. Support the forms:
65 # http://crbug.com/1234
66 # http://crosbug.com/1234
67 # crbug/1234
68 # crosbug/p/1234
69 cls.register_converter(
70 # 1 2 3 4 5 6 7
71 r'\b((http://)?((crbug|crosbug)(\.com)?(/(p/)?[0-9]+)))\b',
72 r'http://\4.com\6', r'\1', False)
73
74 # Convert e-mail addresses.
75 cls.register_converter(
76 r'(([-+.a-z0-9_!#$%&*/=?^_`{|}~]+)@[-a-z0-9.]+\.[a-z0-9]+)\b',
77 r'\1', r'\2', True)
78
79 # Convert SHA1's to gerrit links. Assume all external since
80 # there is no sane way to detect it's an internal CL.
81 cls.register_converter(
82 r'\b([0-9a-f]{40})\b',
83 r'%s/#q,\1,n,z' % cls.GERRIT_URLS['chromium'], r'\1', False)
84
85 # Convert public gerrit CL numbers which take the form:
86 # CL:1234
87 cls.register_converter(
88 r'\b(CL:([0-9]+))\b',
89 r'%s/\2' % cls.GERRIT_URLS['chromium'], r'\1', False)
90 # Convert internal gerrit CL numbers which take the form:
91 # CL:*1234
92 cls.register_converter(
93 r'\b(CL:\*([0-9]+))\b',
94 r'%s/\2' % cls.GERRIT_URLS['chrome'], r'\1', False)
95
96 # Match the string:
97 # Automatic: "cbuildbot" on "x86-generic ASAN" from.
98 # Do this for everyone since "cbuildbot" is unique to CrOS.
99 # Otherwise, we'd do it only for chromium |app_name| instances.
100 cls.register_converter(
101 r'("cbuildbot" on "([^"]+ canary)")',
102 r'%s/builders/\2' % cls.WATERFALL_URLS['chromeos'], r'\1', False)
103 cls.register_converter(
104 r'("cbuildbot" on "([^"]+)")',
105 r'%s/builders/\2' % cls.WATERFALL_URLS['chromiumos'], r'\1', False)
106
107 @classmethod
108 def parse(cls, text):
109 """Creates a list of TextFragment objects based on |text|"""
110 if not text:
111 return []
112 for prog, target, pretty_text, is_email in cls._CONVERTS:
113 m = prog.search(text)
114 if m:
115 link = TextFragment(m.expand(pretty_text),
116 target=m.expand(target),
117 is_email=is_email)
118 left_links = cls.parse(text[:m.start()].rstrip())
119 right_links = cls.parse(text[m.end():].lstrip())
120 return left_links + [link] + right_links
121 return [TextFragment(text)]
122
123 def __init__(self, text):
124 self.raw_text = text
125 self.links = self.parse(text.strip())
126
127 def __str__(self):
128 return self.raw_text
129
130
27 class Status(db.Model): 131 class Status(db.Model):
28 """Description for the status table.""" 132 """Description for the status table."""
29 # The username who added this status. 133 # The username who added this status.
30 username = db.StringProperty(required=True) 134 username = db.StringProperty(required=True)
31 # The date when the status got added. 135 # The date when the status got added.
32 date = db.DateTimeProperty(auto_now_add=True) 136 date = db.DateTimeProperty(auto_now_add=True)
33 # The message. It can contain html code. 137 # The message. It can contain html code.
34 message = db.StringProperty(required=True) 138 message = db.StringProperty(required=True)
35 139
36 @property 140 @property
141 def username_links(self):
142 return LinkableText(self.username)
143
144 @property
145 def message_links(self):
146 return LinkableText(self.message)
147
148 @property
37 def general_state(self): 149 def general_state(self):
38 """Returns a string representing the state that the status message 150 """Returns a string representing the state that the status message
39 describes. 151 describes.
40 """ 152 """
41 message = self.message 153 message = self.message
42 closed = re.search('close', message, re.IGNORECASE) 154 closed = re.search('close', message, re.IGNORECASE)
43 if closed and re.search('maint', message, re.IGNORECASE): 155 if closed and re.search('maint', message, re.IGNORECASE):
44 return 'maintenance' 156 return 'maintenance'
45 if re.search('throt', message, re.IGNORECASE): 157 if re.search('throt', message, re.IGNORECASE):
46 return 'throttled' 158 return 'throttled'
(...skipping 244 matching lines...) Expand 10 before | Expand all | Expand 10 after
291 return self._handle(error_message, last_message) 403 return self._handle(error_message, last_message)
292 else: 404 else:
293 put_status(Status(message=new_message, username=self.user.email())) 405 put_status(Status(message=new_message, username=self.user.email()))
294 self.redirect("/") 406 self.redirect("/")
295 407
296 408
297 def bootstrap(): 409 def bootstrap():
298 # Guarantee that at least one instance exists. 410 # Guarantee that at least one instance exists.
299 if db.GqlQuery('SELECT __key__ FROM Status').get() is None: 411 if db.GqlQuery('SELECT __key__ FROM Status').get() is None:
300 Status(username='none', message='welcome to status').put() 412 Status(username='none', message='welcome to status').put()
413 LinkableText.bootstrap(BasePage.APP_NAME)
OLDNEW
« no previous file with comments | « no previous file | stylesheets/style.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698