blob: 07f657474472ea53fc459eae33df5bcff21cc85b [file] [log] [blame]
[email protected]15f08dd2012-01-27 07:29:481# 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
[email protected]feba21e2012-03-02 15:05:2717 def Append(self, line='', substitute=True):
[email protected]15f08dd2012-01-27 07:29:4818 """Appends a line of code at the current indent level or just a newline if
19 line is not specified. Trailing whitespace is stripped.
[email protected]feba21e2012-03-02 15:05:2720
21 substitute: indicated whether this line should be affected by
22 code.Substitute().
[email protected]15f08dd2012-01-27 07:29:4823 """
[email protected]feba21e2012-03-02 15:05:2724 self._code.append(Line(((' ' * self._indent_level) + line).rstrip(),
25 substitute=substitute))
[email protected]15f08dd2012-01-27 07:29:4826 return self
27
28 def IsEmpty(self):
29 """Returns True if the Code object is empty.
30 """
31 return not bool(self._code)
32
33 def Concat(self, obj):
34 """Concatenate another Code object onto this one. Trailing whitespace is
35 stripped.
36
37 Appends the code at the current indent level. Will fail if there are any
38 un-interpolated format specifiers eg %s, %(something)s which helps
39 isolate any strings that haven't been substituted.
40 """
41 if not isinstance(obj, Code):
[email protected]cfe484902012-02-15 14:52:3242 raise TypeError(type(obj))
[email protected]15f08dd2012-01-27 07:29:4843 assert self is not obj
44 for line in obj._code:
[email protected]cfe484902012-02-15 14:52:3245 try:
46 # line % () will fail if any substitution tokens are left in line
[email protected]feba21e2012-03-02 15:05:2747 if line.substitute:
48 line.value %= ()
[email protected]cfe484902012-02-15 14:52:3249 except TypeError:
50 raise TypeError('Unsubstituted value when concatting\n' + line)
[email protected]feba21e2012-03-02 15:05:2751 except ValueError:
52 raise ValueError('Stray % character when concatting\n' + line)
53 self.Append(line.value, line.substitute)
[email protected]15f08dd2012-01-27 07:29:4854
55 return self
56
57 def Sblock(self, line=''):
58 """Starts a code block.
59
60 Appends a line of code and then increases the indent level.
61 """
62 self.Append(line)
63 self._indent_level += self._indent_size
64 return self
65
66 def Eblock(self, line=''):
67 """Ends a code block by decreasing and then appending a line (or a blank
68 line if not given).
69 """
70 # TODO(calamity): Decide if type checking is necessary
71 #if not isinstance(line, basestring):
72 # raise TypeError
73 self._indent_level -= self._indent_size
74 self.Append(line)
75 return self
76
[email protected]feba21e2012-03-02 15:05:2777 def Comment(self, comment, comment_prefix='// '):
[email protected]15f08dd2012-01-27 07:29:4878 """Adds the given string as a comment.
79
80 Will split the comment if it's too long. Use mainly for variable length
81 comments. Otherwise just use code.Append('// ...') for comments.
[email protected]feba21e2012-03-02 15:05:2782
83 Unaffected by code.Substitute().
[email protected]15f08dd2012-01-27 07:29:4884 """
[email protected]feba21e2012-03-02 15:05:2785 max_len = self._comment_length - self._indent_level - len(comment_prefix)
[email protected]15f08dd2012-01-27 07:29:4886 while len(comment) >= max_len:
87 line = comment[0:max_len]
88 last_space = line.rfind(' ')
89 if last_space != -1:
90 line = line[0:last_space]
91 comment = comment[last_space + 1:]
92 else:
93 comment = comment[max_len:]
[email protected]feba21e2012-03-02 15:05:2794 self.Append(comment_prefix + line, substitute=False)
95 self.Append(comment_prefix + comment, substitute=False)
[email protected]15f08dd2012-01-27 07:29:4896 return self
97
98 def Substitute(self, d):
99 """Goes through each line and interpolates using the given dict.
100
101 Raises type error if passed something that isn't a dict
102
103 Use for long pieces of code using interpolation with the same variables
104 repeatedly. This will reduce code and allow for named placeholders which
105 are more clear.
106 """
107 if not isinstance(d, dict):
108 raise TypeError('Passed argument is not a dictionary: ' + d)
109 for i, line in enumerate(self._code):
[email protected]feba21e2012-03-02 15:05:27110 if self._code[i].substitute:
111 # Only need to check %s because arg is a dict and python will allow
112 # '%s %(named)s' but just about nothing else
113 if '%s' in self._code[i].value or '%r' in self._code[i].value:
114 raise TypeError('"%s" or "%r" found in substitution. '
115 'Named arguments only. Use "%" to escape')
116 self._code[i].value = line.value % d
117 self._code[i].substitute = False
[email protected]15f08dd2012-01-27 07:29:48118 return self
119
120 def Render(self):
121 """Renders Code as a string.
122 """
[email protected]feba21e2012-03-02 15:05:27123 return '\n'.join([l.value for l in self._code])
[email protected]15f08dd2012-01-27 07:29:48124
[email protected]feba21e2012-03-02 15:05:27125class Line(object):
126 """A line of code.
127 """
128 def __init__(self, value, substitute=True):
129 self.value = value
130 self.substitute = substitute