Index: status.py |
diff --git a/status.py b/status.py |
index fc37e40ea896dcbff03fe3c5f8f14ea01ca3670c..317dacb2850e26fb556110073bad2f3592a97e6a 100644 |
--- a/status.py |
+++ b/status.py |
@@ -22,6 +22,102 @@ ALLOWED_ORIGINS = [ |
] |
+class Link(object): |
Vadim Sh.
2013/12/03 20:06:36
I'd convert it to collections.namedtuple. Shorter,
Vadim Sh.
2013/12/03 20:06:36
Also, 'Link' is a misleading name. This object can
vapier
2013/12/04 07:54:33
we discussed collections in the previous patch. a
|
+ """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 'Link({%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 convert known strings for the user. |
Vadim Sh.
2013/12/03 20:06:36
nit: 'linkify convert'. Keep only one?)
vapier
2013/12/04 07:54:33
done
|
+ _CONVERTS = [ |
Vadim Sh.
2013/12/03 20:06:36
Is is possible to get rid of 're.compile' repetiti
vapier
2013/12/04 07:54:33
it is a bit cleaner, and it would provide self doc
|
+ # 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), |
Vadim Sh.
2013/12/03 20:06:36
Why not put 'mailto:' into target (i.e. r'mailto:\
vapier
2013/12/04 07:54:33
"mailto:" is part of the rendering side (i.e. HTML
|
+ |
+ # 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), |
+ |
+ # Do this for everyone since "cbuildbot" is unique to CrOS. |
+ # Otherwise, we'd do this in the bootstrap func below. |
+ # Match the string: |
+ # Automatic: "cbuildbot" on "x86-generic ASAN" from. |
+ (re.compile(r'("cbuildbot" on "([^"]+ canary)")', |
+ flags=re.I), |
+ r'%s/builders/\2' % WATERFALL_URLS['chromeos'], r'\1', False), |
+ (re.compile(r'("cbuildbot" on "([^"]+)")', |
+ flags=re.I), |
+ r'%s/builders/\2' % WATERFALL_URLS['chromiumos'], r'\1', False), |
+ ] |
+ |
+ @classmethod |
+ def bootstrap(cls, _app_name): |
+ """Add conversions specific to |app_name| instance""" |
+ pass |
+ |
+ @classmethod |
+ def parse(cls, text): |
+ """Creates 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 |
Vadim Sh.
2013/12/03 20:06:36
This looks nice and elegant :)
|
+ 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 +127,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. |
+ kwargs['message'] = kwargs.get('message', '').replace('\n', '') |
+ |
+ super(Status, self).__init__(*args, **kwargs) |
+ self.username_links = LinkableText(self.username) |
Vadim Sh.
2013/12/03 20:06:36
__init__ in db.Model subclass is not very conventi
vapier
2013/12/04 07:54:33
for the message issue, i think this would fix it:
|
+ self.message_links = LinkableText(self.message) |
+ |
@property |
def general_state(self): |
"""Returns a string representing the state that the status message |
@@ -296,3 +400,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) |