blob: c23fc483c50cfe90aae6778fbad0a0658ecf1ecb [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 = []
[email protected]15f08dd2012-01-27 07:29:4813 self._indent_size = indent_size
14 self._comment_length = comment_length
rdevlin.cronin9ead75e2015-03-30 17:36:1715 self._line_prefixes = []
[email protected]15f08dd2012-01-27 07:29:4816
rdevlin.cronin9ead75e2015-03-30 17:36:1717 def Append(self, line='',
18 substitute=True,
19 indent_level=None,
20 new_line=True,
21 strip_right=True):
[email protected]15f08dd2012-01-27 07:29:4822 """Appends a line of code at the current indent level or just a newline if
rdevlin.cronin9ead75e2015-03-30 17:36:1723 line is not specified.
[email protected]feba21e2012-03-02 15:05:2724
25 substitute: indicated whether this line should be affected by
26 code.Substitute().
rdevlin.cronin9ead75e2015-03-30 17:36:1727 new_line: whether this should be added as a new line, or should be appended
28 to the last line of the code.
29 strip_right: whether or not trailing whitespace should be stripped.
[email protected]15f08dd2012-01-27 07:29:4830 """
rdevlin.cronin9ead75e2015-03-30 17:36:1731
stevenjbf0e8cce52015-12-10 19:27:3532 if line:
33 prefix = indent_level * ' ' if indent_level else ''.join(
34 self._line_prefixes)
35 else:
36 prefix = ''
rdevlin.cronin9ead75e2015-03-30 17:36:1737
38 if strip_right:
39 line = line.rstrip()
40
41 if not new_line and self._code:
42 self._code[-1].value += line
43 else:
44 self._code.append(Line(prefix + line, substitute=substitute))
[email protected]15f08dd2012-01-27 07:29:4845 return self
46
47 def IsEmpty(self):
48 """Returns True if the Code object is empty.
49 """
50 return not bool(self._code)
51
rdevlin.cronin9ead75e2015-03-30 17:36:1752 def Concat(self, obj, new_line=True):
[email protected]15f08dd2012-01-27 07:29:4853 """Concatenate another Code object onto this one. Trailing whitespace is
54 stripped.
55
56 Appends the code at the current indent level. Will fail if there are any
57 un-interpolated format specifiers eg %s, %(something)s which helps
58 isolate any strings that haven't been substituted.
59 """
60 if not isinstance(obj, Code):
[email protected]cfe484902012-02-15 14:52:3261 raise TypeError(type(obj))
[email protected]15f08dd2012-01-27 07:29:4862 assert self is not obj
rdevlin.cronin9ead75e2015-03-30 17:36:1763 if not obj._code:
64 return self
65
[email protected]15f08dd2012-01-27 07:29:4866 for line in obj._code:
[email protected]cfe484902012-02-15 14:52:3267 try:
68 # line % () will fail if any substitution tokens are left in line
[email protected]feba21e2012-03-02 15:05:2769 if line.substitute:
70 line.value %= ()
[email protected]cfe484902012-02-15 14:52:3271 except TypeError:
[email protected]bee7a7932013-08-12 23:45:3872 raise TypeError('Unsubstituted value when concatting\n' + line.value)
[email protected]feba21e2012-03-02 15:05:2773 except ValueError:
[email protected]bee7a7932013-08-12 23:45:3874 raise ValueError('Stray % character when concatting\n' + line.value)
rdevlin.cronin9ead75e2015-03-30 17:36:1775 first_line = obj._code.pop(0)
76 self.Append(first_line.value, first_line.substitute, new_line=new_line)
77 for line in obj._code:
[email protected]feba21e2012-03-02 15:05:2778 self.Append(line.value, line.substitute)
[email protected]15f08dd2012-01-27 07:29:4879
80 return self
81
[email protected]242d5e7a2013-01-17 06:50:3182 def Cblock(self, code):
83 """Concatenates another Code object |code| onto this one followed by a
84 blank line, if |code| is non-empty."""
85 if not code.IsEmpty():
86 self.Concat(code).Append()
87 return self
88
rdevlin.cronin9ead75e2015-03-30 17:36:1789 def Sblock(self, line=None, line_prefix=None, new_line=True):
[email protected]15f08dd2012-01-27 07:29:4890 """Starts a code block.
91
rdevlin.cronin9ead75e2015-03-30 17:36:1792 Appends a line of code and then increases the indent level. If |line_prefix|
93 is present, it will be treated as the extra prefix for the code block.
94 Otherwise, the prefix will be the default indent level.
[email protected]15f08dd2012-01-27 07:29:4895 """
[email protected]32096af2013-02-06 01:29:3196 if line is not None:
rdevlin.cronin9ead75e2015-03-30 17:36:1797 self.Append(line, new_line=new_line)
98 self._line_prefixes.append(line_prefix or ' ' * self._indent_size)
[email protected]15f08dd2012-01-27 07:29:4899 return self
100
[email protected]32096af2013-02-06 01:29:31101 def Eblock(self, line=None):
[email protected]15f08dd2012-01-27 07:29:48102 """Ends a code block by decreasing and then appending a line (or a blank
103 line if not given).
104 """
105 # TODO(calamity): Decide if type checking is necessary
106 #if not isinstance(line, basestring):
107 # raise TypeError
rdevlin.cronin9ead75e2015-03-30 17:36:17108 self._line_prefixes.pop()
[email protected]32096af2013-02-06 01:29:31109 if line is not None:
110 self.Append(line)
[email protected]15f08dd2012-01-27 07:29:48111 return self
112
rdevlin.cronin9ead75e2015-03-30 17:36:17113 def Comment(self, comment, comment_prefix='// ',
114 wrap_indent=0, new_line=True):
[email protected]15f08dd2012-01-27 07:29:48115 """Adds the given string as a comment.
116
117 Will split the comment if it's too long. Use mainly for variable length
118 comments. Otherwise just use code.Append('// ...') for comments.
[email protected]feba21e2012-03-02 15:05:27119
120 Unaffected by code.Substitute().
[email protected]15f08dd2012-01-27 07:29:48121 """
rdevlin.cronin8ea16e12015-03-27 00:13:13122 # Helper function to trim a comment to the maximum length, and return one
123 # line and the remainder of the comment.
124 def trim_comment(comment, max_len):
125 if len(comment) <= max_len:
126 return comment, ''
Mike Frysingerece1fa92020-06-03 19:40:49127 # If we ran out of space due to existing content, don't try to wrap.
128 if max_len <= 1:
129 return '', comment.lstrip()
rdevlin.cronin8ea16e12015-03-27 00:13:13130 last_space = comment.rfind(' ', 0, max_len + 1)
[email protected]15f08dd2012-01-27 07:29:48131 if last_space != -1:
rdevlin.cronin8ea16e12015-03-27 00:13:13132 line = comment[0:last_space]
[email protected]15f08dd2012-01-27 07:29:48133 comment = comment[last_space + 1:]
134 else:
Mike Frysingerece1fa92020-06-03 19:40:49135 # If the line can't be split, then don't try. The comments might be
136 # important (e.g. JSDoc) where splitting it breaks things.
137 line = comment
138 comment = ''
stevenjb74d53a0e2016-01-21 00:08:09139 return line, comment.lstrip()
rdevlin.cronin8ea16e12015-03-27 00:13:13140
141 # First line has the full maximum length.
rdevlin.cronin9ead75e2015-03-30 17:36:17142 if not new_line and self._code:
rdevlin.cronin684111162015-04-07 17:20:59143 max_len = self._comment_length - len(self._code[-1].value)
rdevlin.cronin9ead75e2015-03-30 17:36:17144 else:
145 max_len = (self._comment_length - len(''.join(self._line_prefixes)) -
146 len(comment_prefix))
rdevlin.cronin8ea16e12015-03-27 00:13:13147 line, comment = trim_comment(comment, max_len)
rdevlin.cronin9ead75e2015-03-30 17:36:17148 self.Append(comment_prefix + line, substitute=False, new_line=new_line)
rdevlin.cronin8ea16e12015-03-27 00:13:13149
150 # Any subsequent lines be subject to the wrap indent.
rdevlin.cronin9ead75e2015-03-30 17:36:17151 max_len = (self._comment_length - len(''.join(self._line_prefixes)) -
152 len(comment_prefix) - wrap_indent)
Mike Frysingerece1fa92020-06-03 19:40:49153 assert max_len > 1
rdevlin.cronin8ea16e12015-03-27 00:13:13154 while len(comment):
155 line, comment = trim_comment(comment, max_len)
156 self.Append(comment_prefix + (' ' * wrap_indent) + line, substitute=False)
157
[email protected]15f08dd2012-01-27 07:29:48158 return self
159
160 def Substitute(self, d):
161 """Goes through each line and interpolates using the given dict.
162
163 Raises type error if passed something that isn't a dict
164
165 Use for long pieces of code using interpolation with the same variables
166 repeatedly. This will reduce code and allow for named placeholders which
167 are more clear.
168 """
169 if not isinstance(d, dict):
170 raise TypeError('Passed argument is not a dictionary: ' + d)
171 for i, line in enumerate(self._code):
[email protected]feba21e2012-03-02 15:05:27172 if self._code[i].substitute:
173 # Only need to check %s because arg is a dict and python will allow
174 # '%s %(named)s' but just about nothing else
175 if '%s' in self._code[i].value or '%r' in self._code[i].value:
176 raise TypeError('"%s" or "%r" found in substitution. '
177 'Named arguments only. Use "%" to escape')
178 self._code[i].value = line.value % d
179 self._code[i].substitute = False
[email protected]15f08dd2012-01-27 07:29:48180 return self
181
stevenjbf0e8cce52015-12-10 19:27:35182 def TrimTrailingNewlines(self):
stevenjb74d53a0e2016-01-21 00:08:09183 """Removes any trailing empty Line objects.
stevenjbf0e8cce52015-12-10 19:27:35184 """
185 while self._code:
186 if self._code[-1].value != '':
187 return
188 self._code = self._code[:-1]
189
[email protected]15f08dd2012-01-27 07:29:48190 def Render(self):
191 """Renders Code as a string.
192 """
[email protected]feba21e2012-03-02 15:05:27193 return '\n'.join([l.value for l in self._code])
[email protected]15f08dd2012-01-27 07:29:48194
[email protected]0853bcf2013-11-06 05:14:33195
[email protected]feba21e2012-03-02 15:05:27196class Line(object):
197 """A line of code.
198 """
199 def __init__(self, value, substitute=True):
200 self.value = value
201 self.substitute = substitute