195640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org#!/usr/bin/env python
295640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org# Copyright (c) 2012 The Chromium Authors. All rights reserved.
301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# Use of this source code is governed by a BSD-style license that can be
401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# found in the LICENSE file.
501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''Adaptation of the extern.tclib classes for our needs.
701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''
801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport re
1101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport types
1201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import exception
1401fadb72b6e94e6511eaffd1874a8cc095f098a7joi@chromium.orgfrom grit import lazy_re
1501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgimport grit.extern.tclib
1601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
178a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org
188a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org# Matches whitespace sequences which can be folded into a single whitespace
198a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org# character.  This matches single characters so that non-spaces are replaced
208a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org# with spaces.
218a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org_FOLD_WHITESPACE = re.compile(r'\s+')
228a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org
238a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org
2401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgdef Identity(i):
2501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  return i
2601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
2801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass BaseMessage(object):
2901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''Base class with methods shared by Message and Translation.
3001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''
3101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
3201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def __init__(self, text='', placeholders=[], description='', meaning=''):
3301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.parts = []
3401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.placeholders = []
3501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.meaning = meaning
3601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.dirty = True  # True if self.id is (or might be) wrong
3701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.id = 0
388a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org    self.SetDescription(description)
3901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
4001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if text != '':
4101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if not placeholders or placeholders == []:
4201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        self.AppendText(text)
4301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
4401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        tag_map = {}
4501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        for placeholder in placeholders:
4601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          tag_map[placeholder.GetPresentation()] = [placeholder, 0]
476f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org        # This creates a regexp like '(TAG1|TAG2|TAG3)'.
486f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org        # The tags have to be sorted in order of decreasing length, so that
496f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org        # longer tags are substituted before shorter tags that happen to be
506f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org        # substrings of the longer tag.
516f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org        # E.g. "EXAMPLE_FOO_NAME" must be matched before "EXAMPLE_FOO",
526f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org        # otherwise "EXAMPLE_FOO" splits "EXAMPLE_FOO_NAME" too.
536f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org        tags = tag_map.keys()
546f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org        tags.sort(cmp=lambda x,y: len(x) - len(y) or cmp(x, y), reverse=True)
556f54ac22395af534c8ccfe6834eedb636f9500dbjoaodasilva@chromium.org        tag_re = '(' + '|'.join(tags) + ')'
5601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        chunked_text = re.split(tag_re, text)
5701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        for chunk in chunked_text:
5801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          if chunk: # ignore empty chunk
5901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            if tag_map.has_key(chunk):
6001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org              self.AppendPlaceholder(tag_map[chunk][0])
6101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org              tag_map[chunk][1] += 1 # increase placeholder use count
6201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org            else:
6301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org              self.AppendText(chunk)
6401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        for key in tag_map.keys():
6501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          assert tag_map[key][1] != 0
6601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
6701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetRealContent(self, escaping_function=Identity):
6801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the original content, i.e. what your application and users
6901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    will see.
7001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
7101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Specify a function to escape each translateable bit, if you like.
7201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
7301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    bits = []
7401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for item in self.parts:
7501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if isinstance(item, types.StringTypes):
7601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        bits.append(escaping_function(item))
7701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
7801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        bits.append(item.GetOriginal())
7901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return ''.join(bits)
8001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
8101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetPresentableContent(self):
8201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    presentable_content = []
8301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for part in self.parts:
8401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if isinstance(part, Placeholder):
8501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        presentable_content.append(part.GetPresentation())
8601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
8701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        presentable_content.append(part)
8801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return ''.join(presentable_content)
8901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
9001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def AppendPlaceholder(self, placeholder):
9101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert isinstance(placeholder, Placeholder)
9201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    dup = False
9301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for other in self.GetPlaceholders():
9401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if other.presentation == placeholder.presentation:
9501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        assert other.original == placeholder.original
9601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        dup = True
9701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
9801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if not dup:
9901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.placeholders.append(placeholder)
10001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.parts.append(placeholder)
10101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.dirty = True
10201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def AppendText(self, text):
10401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert isinstance(text, types.StringTypes)
10501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert text != ''
10601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
10701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.parts.append(text)
10801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.dirty = True
10901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
11001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetContent(self):
11101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Returns the parts of the message.  You may modify parts if you wish.
11201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Note that you must not call GetId() on this object until you have finished
11301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    modifying the contents.
11401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
11501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.dirty = True  # user might modify content
11601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.parts
11701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
11801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetDescription(self):
11901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.description
12001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
12101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def SetDescription(self, description):
1228a303dc2cabedfd395b49f21310ea21c77056fe7joi@chromium.org    self.description = _FOLD_WHITESPACE.sub(' ', description)
12301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
12401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetMeaning(self):
12501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.meaning
12601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
12701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetId(self):
12801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if self.dirty:
12901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.id = self.GenerateId()
13001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.dirty = False
13101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.id
13201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
13301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GenerateId(self):
13401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Must use a UTF-8 encoded version of the presentable content, along with
13501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # the meaning attribute, to match the TC.
13601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return grit.extern.tclib.GenerateMessageId(
13701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      self.GetPresentableContent().encode('utf-8'), self.meaning)
13801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
13901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetPlaceholders(self):
14001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.placeholders
14101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def FillTclibBaseMessage(self, msg):
14301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    msg.SetDescription(self.description.encode('utf-8'))
14401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    for part in self.parts:
14601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if isinstance(part, Placeholder):
14701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        ph = grit.extern.tclib.Placeholder(
14801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          part.presentation.encode('utf-8'),
14901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          part.original.encode('utf-8'),
15001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          part.example.encode('utf-8'))
15101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        msg.AppendPlaceholder(ph)
15201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
15301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        msg.AppendText(part.encode('utf-8'))
15401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
15501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
15601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass Message(BaseMessage):
15701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''A message.'''
15801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
15901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def __init__(self, text='', placeholders=[], description='', meaning='',
16001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org               assigned_id=None):
161b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org    super(Message, self).__init__(text, placeholders, description, meaning)
16201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.assigned_id = assigned_id
16301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
16401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def ToTclibMessage(self):
16501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    msg = grit.extern.tclib.Message('utf-8', meaning=self.meaning)
16601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.FillTclibBaseMessage(msg)
16701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return msg
16801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
16901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetId(self):
17001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Use the assigned id if we have one.'''
17101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if self.assigned_id:
17201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      return self.assigned_id
17301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
174b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org    return super(Message, self).GetId()
17501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
176ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org  def HasAssignedId(self):
177ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org    '''Returns True if this message has an assigned id.'''
178ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org    return bool(self.assigned_id)
179ca6c225d0059dd17b476c934e744a3c4f60e2df2joi@chromium.org
18001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
18101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass Translation(BaseMessage):
18201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''A translation.'''
18301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
18401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def __init__(self, text='', id='', placeholders=[], description='', meaning=''):
185b9161407f737461b5db16a29782f8a31d19e602dbenrg@chromium.org    super(Translation, self).__init__(text, placeholders, description, meaning)
18601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.id = id
18701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
18801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetId(self):
18901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert id != '', "ID has not been set."
19001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.id
19101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
19201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def SetId(self, id):
19301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.id = id
19401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
19501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def ToTclibMessage(self):
19601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    msg = grit.extern.tclib.Message(
19701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      'utf-8', id=self.id, meaning=self.meaning)
19801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.FillTclibBaseMessage(msg)
19901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return msg
20001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
20101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
20201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgclass Placeholder(grit.extern.tclib.Placeholder):
20301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''Modifies constructor to accept a Unicode string
20401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  '''
20501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
20601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  # Must match placeholder presentation names
20701fadb72b6e94e6511eaffd1874a8cc095f098a7joi@chromium.org  _NAME_RE = lazy_re.compile('^[A-Za-z0-9_]+$')
20801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
20901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def __init__(self, presentation, original, example):
21001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''Creates a new placeholder.
21101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
21201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    Args:
21301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      presentation: 'USERNAME'
21401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      original: '%s'
21501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      example: 'Joi'
21601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    '''
21701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert presentation != ''
21801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert original != ''
21901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    assert example != ''
22001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    if not self._NAME_RE.match(presentation):
22101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      raise exception.InvalidPlaceholderName(presentation)
22201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.presentation = presentation
22301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.original = original
22401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    self.example = example
22501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
22601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetPresentation(self):
22701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.presentation
22801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
22901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetOriginal(self):
23001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.original
23101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
23201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org  def GetExample(self):
23301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    return self.example
23401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
23501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
236