15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# found in the LICENSE file.
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Code(object):
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """A convenience object for constructing code.
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Logically each object should be a block of code. All methods except |Render|
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  and |IsEmpty| return self.
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, indent_size=2, comment_length=80):
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._code = []
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._indent_level = 0
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._indent_size = indent_size
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._comment_length = comment_length
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def Append(self, line='', substitute=True, indent_level=None):
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Appends a line of code at the current indent level or just a newline if
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    line is not specified. Trailing whitespace is stripped.
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    substitute: indicated whether this line should be affected by
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    code.Substitute().
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
242a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if indent_level is None:
252a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      indent_level = self._indent_level
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    self._code.append(Line(((' ' * indent_level) + line).rstrip(),
272a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                      substitute=substitute))
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def IsEmpty(self):
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Returns True if the Code object is empty.
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return not bool(self._code)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Concat(self, obj):
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Concatenate another Code object onto this one. Trailing whitespace is
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    stripped.
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Appends the code at the current indent level. Will fail if there are any
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    un-interpolated format specifiers eg %s, %(something)s which helps
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    isolate any strings that haven't been substituted.
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not isinstance(obj, Code):
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise TypeError(type(obj))
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    assert self is not obj
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for line in obj._code:
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      try:
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # line % () will fail if any substitution tokens are left in line
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if line.substitute:
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          line.value %= ()
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      except TypeError:
52c2db58bd994c04d98e4ee2cd7565b71548655fe3Ben Murdoch        raise TypeError('Unsubstituted value when concatting\n' + line.value)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      except ValueError:
54c2db58bd994c04d98e4ee2cd7565b71548655fe3Ben Murdoch        raise ValueError('Stray % character when concatting\n' + line.value)
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.Append(line.value, line.substitute)
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def Cblock(self, code):
602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    """Concatenates another Code object |code| onto this one followed by a
612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    blank line, if |code| is non-empty."""
622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if not code.IsEmpty():
632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.Concat(code).Append()
642a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    return self
652a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
662a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def Sblock(self, line=None):
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Starts a code block.
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Appends a line of code and then increases the indent level.
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
712a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if line is not None:
722a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.Append(line)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._indent_level += self._indent_size
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
762a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)  def Eblock(self, line=None):
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Ends a code block by decreasing and then appending a line (or a blank
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    line if not given).
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    # TODO(calamity): Decide if type checking is necessary
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    #if not isinstance(line, basestring):
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    #  raise TypeError
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self._indent_level -= self._indent_size
842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    if line is not None:
852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)      self.Append(line)
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Comment(self, comment, comment_prefix='// '):
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Adds the given string as a comment.
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Will split the comment if it's too long. Use mainly for variable length
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    comments. Otherwise just use code.Append('// ...') for comments.
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Unaffected by code.Substitute().
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    max_len = self._comment_length - self._indent_level - len(comment_prefix)
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    while len(comment) >= max_len:
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      line = comment[0:max_len]
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      last_space = line.rfind(' ')
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if last_space != -1:
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        line = line[0:last_space]
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        comment = comment[last_space + 1:]
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else:
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        comment = comment[max_len:]
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.Append(comment_prefix + line, substitute=False)
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.Append(comment_prefix + comment, substitute=False)
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Substitute(self, d):
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Goes through each line and interpolates using the given dict.
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Raises type error if passed something that isn't a dict
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Use for long pieces of code using interpolation with the same variables
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    repeatedly. This will reduce code and allow for named placeholders which
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    are more clear.
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if not isinstance(d, dict):
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      raise TypeError('Passed argument is not a dictionary: ' + d)
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for i, line in enumerate(self._code):
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if self._code[i].substitute:
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Only need to check %s because arg is a dict and python will allow
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # '%s %(named)s' but just about nothing else
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if '%s' in self._code[i].value or '%r' in self._code[i].value:
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          raise TypeError('"%s" or "%r" found in substitution. '
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                          'Named arguments only. Use "%" to escape')
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._code[i].value = line.value % d
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self._code[i].substitute = False
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return self
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def Render(self):
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Renders Code as a string.
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return '\n'.join([l.value for l in self._code])
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
136f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class Line(object):
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """A line of code.
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  """
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  def __init__(self, value, substitute=True):
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.value = value
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    self.substitute = substitute
143