1# Copyright (c) 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5class Code(object): 6 """A convenience object for constructing code. 7 8 Logically each object should be a block of code. All methods except |Render| 9 and |IsEmpty| return self. 10 """ 11 def __init__(self, indent_size=2, comment_length=80): 12 self._code = [] 13 self._indent_level = 0 14 self._indent_size = indent_size 15 self._comment_length = comment_length 16 17 def Append(self, line='', substitute=True, indent_level=None): 18 """Appends a line of code at the current indent level or just a newline if 19 line is not specified. Trailing whitespace is stripped. 20 21 substitute: indicated whether this line should be affected by 22 code.Substitute(). 23 """ 24 if indent_level is None: 25 indent_level = self._indent_level 26 self._code.append(Line(((' ' * indent_level) + line).rstrip(), 27 substitute=substitute)) 28 return self 29 30 def IsEmpty(self): 31 """Returns True if the Code object is empty. 32 """ 33 return not bool(self._code) 34 35 def Concat(self, obj): 36 """Concatenate another Code object onto this one. Trailing whitespace is 37 stripped. 38 39 Appends the code at the current indent level. Will fail if there are any 40 un-interpolated format specifiers eg %s, %(something)s which helps 41 isolate any strings that haven't been substituted. 42 """ 43 if not isinstance(obj, Code): 44 raise TypeError(type(obj)) 45 assert self is not obj 46 for line in obj._code: 47 try: 48 # line % () will fail if any substitution tokens are left in line 49 if line.substitute: 50 line.value %= () 51 except TypeError: 52 raise TypeError('Unsubstituted value when concatting\n' + line.value) 53 except ValueError: 54 raise ValueError('Stray % character when concatting\n' + line.value) 55 self.Append(line.value, line.substitute) 56 57 return self 58 59 def Cblock(self, code): 60 """Concatenates another Code object |code| onto this one followed by a 61 blank line, if |code| is non-empty.""" 62 if not code.IsEmpty(): 63 self.Concat(code).Append() 64 return self 65 66 def Sblock(self, line=None): 67 """Starts a code block. 68 69 Appends a line of code and then increases the indent level. 70 """ 71 if line is not None: 72 self.Append(line) 73 self._indent_level += self._indent_size 74 return self 75 76 def Eblock(self, line=None): 77 """Ends a code block by decreasing and then appending a line (or a blank 78 line if not given). 79 """ 80 # TODO(calamity): Decide if type checking is necessary 81 #if not isinstance(line, basestring): 82 # raise TypeError 83 self._indent_level -= self._indent_size 84 if line is not None: 85 self.Append(line) 86 return self 87 88 def Comment(self, comment, comment_prefix='// '): 89 """Adds the given string as a comment. 90 91 Will split the comment if it's too long. Use mainly for variable length 92 comments. Otherwise just use code.Append('// ...') for comments. 93 94 Unaffected by code.Substitute(). 95 """ 96 max_len = self._comment_length - self._indent_level - len(comment_prefix) 97 while len(comment) >= max_len: 98 line = comment[0:max_len] 99 last_space = line.rfind(' ') 100 if last_space != -1: 101 line = line[0:last_space] 102 comment = comment[last_space + 1:] 103 else: 104 comment = comment[max_len:] 105 self.Append(comment_prefix + line, substitute=False) 106 self.Append(comment_prefix + comment, substitute=False) 107 return self 108 109 def Substitute(self, d): 110 """Goes through each line and interpolates using the given dict. 111 112 Raises type error if passed something that isn't a dict 113 114 Use for long pieces of code using interpolation with the same variables 115 repeatedly. This will reduce code and allow for named placeholders which 116 are more clear. 117 """ 118 if not isinstance(d, dict): 119 raise TypeError('Passed argument is not a dictionary: ' + d) 120 for i, line in enumerate(self._code): 121 if self._code[i].substitute: 122 # Only need to check %s because arg is a dict and python will allow 123 # '%s %(named)s' but just about nothing else 124 if '%s' in self._code[i].value or '%r' in self._code[i].value: 125 raise TypeError('"%s" or "%r" found in substitution. ' 126 'Named arguments only. Use "%" to escape') 127 self._code[i].value = line.value % d 128 self._code[i].substitute = False 129 return self 130 131 def Render(self): 132 """Renders Code as a string. 133 """ 134 return '\n'.join([l.value for l in self._code]) 135 136 137class Line(object): 138 """A line of code. 139 """ 140 def __init__(self, value, substitute=True): 141 self.value = value 142 self.substitute = substitute 143