Index: status.py |
diff --git a/status.py b/status.py |
index 90f01b2d4baa91afbf50e3662f3eae6fe2f6c5ad..05335cb6b1aa99625da7b315d1222fc0386cd570 100644 |
--- a/status.py |
+++ b/status.py |
@@ -24,6 +24,110 @@ ALLOWED_ORIGINS = [ |
] |
+class TextFragment(object): |
+ """Simple object to hold text that might be linked""" |
+ |
+ def __init__(self, text, target=None, is_email=False): |
+ self.text = text |
+ self.target = target |
+ self.is_email = is_email |
+ |
+ def __repr__(self): |
+ return 'TextFragment({%s->%s})' % (self.text, self.target) |
+ |
+ |
+class LinkableText(object): |
+ """Turns arbitrary text into a set of links""" |
+ |
+ GERRIT_URLS = { |
+ 'chrome': 'https://chrome-internal-review.googlesource.com', |
+ 'chromium': 'https://chromium-review.googlesource.com', |
+ } |
+ |
+ WATERFALL_URLS = { |
+ 'chromeos': 'http://chromegw/i/chromeos', |
+ 'chromiumos': 'http://build.chromium.org/p/chromiumos', |
+ } |
+ |
+ # Automatically linkify known strings for the user. |
+ _CONVERTS = [] |
+ |
+ @classmethod |
+ def register_converter(cls, regex, target, pretty, is_email, flags=re.I): |
+ """Register a new conversion for creating links from text""" |
+ cls._CONVERTS.append( |
+ (re.compile(regex, flags=flags), target, pretty, is_email)) |
+ |
+ @classmethod |
+ def bootstrap(cls, _app_name): |
+ """Add conversions (possibly specific to |app_name| instance)""" |
+ # Convert CrOS bug links. Support the forms: |
+ # http://crbug.com/1234 |
+ # http://crosbug.com/1234 |
+ # crbug/1234 |
+ # crosbug/p/1234 |
+ cls.register_converter( |
+ # 1 2 3 4 5 6 7 |
+ r'\b((http://)?((crbug|crosbug)(\.com)?(/(p/)?[0-9]+)))\b', |
+ r'http://\4.com\6', r'\1', False) |
+ |
+ # Convert e-mail addresses. |
+ cls.register_converter( |
+ r'(([-+.a-z0-9_!#$%&*/=?^_`{|}~]+)@[-a-z0-9.]+\.[a-z0-9]+)\b', |
+ 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. |
+ cls.register_converter( |
+ r'\b([0-9a-f]{40})\b', |
+ r'%s/#q,\1,n,z' % cls.GERRIT_URLS['chromium'], r'\1', False) |
+ |
+ # Convert public gerrit CL numbers which take the form: |
+ # CL:1234 |
+ cls.register_converter( |
+ r'\b(CL:([0-9]+))\b', |
+ r'%s/\2' % cls.GERRIT_URLS['chromium'], r'\1', False) |
+ # Convert internal gerrit CL numbers which take the form: |
+ # CL:*1234 |
+ cls.register_converter( |
+ r'\b(CL:\*([0-9]+))\b', |
+ r'%s/\2' % cls.GERRIT_URLS['chrome'], r'\1', False) |
+ |
+ # Match the string: |
+ # Automatic: "cbuildbot" on "x86-generic ASAN" from. |
+ # Do this for everyone since "cbuildbot" is unique to CrOS. |
+ # Otherwise, we'd do it only for chromium |app_name| instances. |
+ cls.register_converter( |
+ r'("cbuildbot" on "([^"]+ canary)")', |
+ r'%s/builders/\2' % cls.WATERFALL_URLS['chromeos'], r'\1', False) |
+ cls.register_converter( |
+ r'("cbuildbot" on "([^"]+)")', |
+ r'%s/builders/\2' % cls.WATERFALL_URLS['chromiumos'], r'\1', False) |
+ |
+ @classmethod |
+ def parse(cls, text): |
+ """Creates a list of TextFragment 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 = TextFragment(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 [TextFragment(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. |
@@ -34,6 +138,14 @@ class Status(db.Model): |
message = db.StringProperty(required=True) |
@property |
+ def username_links(self): |
+ return LinkableText(self.username) |
+ |
+ @property |
+ def message_links(self): |
+ return LinkableText(self.message) |
+ |
+ @property |
def general_state(self): |
"""Returns a string representing the state that the status message |
describes. |
@@ -298,3 +410,4 @@ def bootstrap(): |
# Guarantee that at least one instance exists. |
if db.GqlQuery('SELECT __key__ FROM Status').get() is None: |
Status(username='none', message='welcome to status').put() |
+ LinkableText.bootstrap(BasePage.APP_NAME) |