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

Unified 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: fix is_email check Created 7 years, 1 month 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | stylesheets/style.css » ('j') | templates/main.html » ('J')
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: status.py
diff --git a/status.py b/status.py
index fc37e40ea896dcbff03fe3c5f8f14ea01ca3670c..2f8a00aa6ad9bdf4b36c40304fd7da1c2c8988ff 100644
--- a/status.py
+++ b/status.py
@@ -22,6 +22,101 @@ ALLOWED_ORIGINS = [
]
+class Link(object):
+ """Simple object to hold text that might be linked"""
M-A Ruel 2013/11/25 16:18:14 """Holds text .. No need to state a relative qual
vapier 2013/11/25 17:51:26 i agree it's simple, but i find scanning a one lin
+
+ def __init__(self, text, target=None, is_email=False):
M-A Ruel 2013/11/25 16:18:14 I'd prefer to not use default arguments.
vapier 2013/11/25 17:51:26 any reason why ? the intention is for email to be
+ self.text = text
+ self.target = target
+ self.is_email = is_email
+
+ def __repr__(self):
+ return 'Link({%s->%s})' % (self.text, self.target)
+
+
+class LinkableText(object):
+ """Turn arbitrary text into a set of links"""
M-A Ruel 2013/11/25 16:18:14 Turns (imperative everywhere)
vapier 2013/11/25 17:51:26 i dont see the point, but i also dont really care
+
+ GERRIT_URLS = {
+ 'chrome': 'https://chrome-internal-review.googlesource.com',
M-A Ruel 2013/11/25 16:18:14 Very hardcoded. :/ Could these be entities?
vapier 2013/11/25 17:51:26 i have no idea what "entities" are. plus this fil
+ 'chromium': 'https://chromium-review.googlesource.com',
+ }
+
+ WATERFALL_URLS = {
+ 'chromeos': 'http://chromegw/i/chromeos',
+ 'chromiumos': 'http://build.chromium.org/p/chromiumos',
+ }
+
+ # Automatically linkify convert known strings for the user.
+ _CONVERTS = [
+ # Convert CrOS bug links. Support the forms:
+ # http://crbug.com/1234
+ # http://crosbug.com/1234
+ # crbug/1234
+ # crosbug/p/1234
+ (re.compile(
+ # 1 2 3 4 5 6 7
+ r'\b((http://)?((crbug|crosbug)(\.com)?(/(p/)?[0-9]+)))\b',
+ flags=re.I),
+ r'http://\4.com\6', r'\1', False),
+
+ # Convert e-mail addresses.
+ (re.compile(r'(([-+.a-z0-9_!#$%&*/=?^_`{|}~]+)@[-a-z0-9.]+\.[a-z0-9]+)\b',
+ flags=re.I),
+ r'\1', r'\2', True),
+
+ # Convert SHA1's to gerrit links. Assume all external since
+ # there is no sane way to detect it's an internal CL.
+ (re.compile(r'\b([0-9a-f]{40})\b', flags=re.I),
+ r'%s/#q,\1,n,z' % GERRIT_URLS['chromium'], r'\1', False),
+
+ # Convert public gerrit CL numbers.
+ (re.compile(r'\b(CL:([0-9]+))\b', flags=re.I),
+ r'%s/\2' % GERRIT_URLS['chromium'], r'\1', False),
+ # Convert internal gerrit CL numbers.
+ (re.compile(r'\b(CL:\*([0-9]+))\b', flags=re.I),
+ r'%s/\2' % GERRIT_URLS['chrome'], r'\1', False),
+ ]
+
+ @classmethod
+ def bootstrap(cls, _app_name):
+ """Add conversions specific to |app_name| instance"""
+ cls._CONVERTS += [
+ # Do this for everyone since "cbuildbot" is unique to CrOS.
+ # Match the string:
+ # Automatic: "cbuildbot" on "x86-generic ASAN" from.
+ (re.compile(r'("cbuildbot" on "([^"]+ canary)")',
M-A Ruel 2013/11/25 16:18:14 These should be stated as static lists. There isn'
vapier 2013/11/25 17:51:26 this was needed in a previous CL where i used a fu
+ flags=re.I),
+ r'%s/builders/\2' % cls.WATERFALL_URLS['chromeos'], r'\1', False),
+ (re.compile(r'("cbuildbot" on "([^"]+)")',
+ flags=re.I),
+ r'%s/builders/\2' % cls.WATERFALL_URLS['chromiumos'], r'\1', False),
+ ]
+
+ @classmethod
+ def parse(cls, text):
+ """Create a list of Link objects based on |text|"""
+ if not text:
+ return []
+ for prog, target, pretty_text, is_email in cls._CONVERTS:
+ m = prog.search(text)
+ if m:
+ link = Link(m.expand(pretty_text),
+ target=m.expand(target),
+ is_email=is_email)
+ left_links = cls.parse(text[:m.start()].rstrip())
+ right_links = cls.parse(text[m.end():].lstrip())
+ return left_links + [link] + right_links
+ return [Link(text)]
+
+ def __init__(self, text):
+ self.raw_text = text
+ self.links = self.parse(text.strip())
+
+ def __str__(self):
+ return self.raw_text
+
+
class Status(db.Model):
"""Description for the status table."""
# The username who added this status.
@@ -31,6 +126,14 @@ class Status(db.Model):
# The message. It can contain html code.
message = db.StringProperty(required=True)
+ def __init__(self, *args, **kwargs):
+ # Normalize newlines otherwise the DB store barfs.
M-A Ruel 2013/11/25 16:18:14 I'm fine with adding multiline=True instead. Actua
vapier 2013/11/25 17:51:26 i don't think we want people to insert newlines in
+ kwargs['message'] = kwargs.get('message', '').replace('\n', '')
+
+ super(Status, self).__init__(*args, **kwargs)
+ self.username_links = LinkableText(self.username)
+ self.message_links = LinkableText(self.message)
+
@property
def general_state(self):
"""Returns a string representing the state that the status message
@@ -246,6 +349,8 @@ class MainPage(BasePage):
def _handle(self, error_message='', last_message=''):
"""Sets the information to be displayed on the main page."""
+ LinkableText.bootstrap(self.APP_NAME)
M-A Ruel 2013/11/25 16:18:14 This is a bad idea to do this there. It creates a
vapier 2013/11/25 17:51:26 i'm not terribly familiar with the app engine mode
+
try:
limit = min(max(int(self.request.get('limit')), 1), 1000)
except ValueError:
« no previous file with comments | « no previous file | stylesheets/style.css » ('j') | templates/main.html » ('J')

Powered by Google App Engine
This is Rietveld 408576698