blob: 10a5d884ad737340115bf64cac6875fe6941f077 [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]4636c832013-01-11 02:10:1117 def Append(self, line='', substitute=True, indent_level=None):
[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]4636c832013-01-11 02:10:1124 if indent_level is None:
25 indent_level = self._indent_level
26 self._code.append(Line(((' ' * indent_level) + line).rstrip(),
27 substitute=substitute))
[email protected]15f08dd2012-01-27 07:29:4828 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):
[email protected]cfe484902012-02-15 14:52:3244 raise TypeError(type(obj))
[email protected]15f08dd2012-01-27 07:29:4845 assert self is not obj
46 for line in obj._code:
[email protected]cfe484902012-02-15 14:52:3247 try:
48 # line % () will fail if any substitution tokens are left in line
[email protected]feba21e2012-03-02 15:05:2749 if line.substitute:
50 line.value %= ()
[email protected]cfe484902012-02-15 14:52:3251 except TypeError:
[email protected]bee7a7932013-08-12 23:45:3852 raise TypeError('Unsubstituted value when concatting\n' + line.value)
[email protected]feba21e2012-03-02 15:05:2753 except ValueError:
[email protected]bee7a7932013-08-12 23:45:3854 raise ValueError('Stray % character when concatting\n' + line.value)
[email protected]feba21e2012-03-02 15:05:2755 self.Append(line.value, line.substitute)
[email protected]15f08dd2012-01-27 07:29:4856
57 return self
58
[email protected]242d5e7a2013-01-17 06:50:3159 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
[email protected]32096af2013-02-06 01:29:3166 def Sblock(self, line=None):
[email protected]15f08dd2012-01-27 07:29:4867 """Starts a code block.
68
69 Appends a line of code and then increases the indent level.
70 """
[email protected]32096af2013-02-06 01:29:3171 if line is not None:
72 self.Append(line)
[email protected]15f08dd2012-01-27 07:29:4873 self._indent_level += self._indent_size
74 return self
75
[email protected]32096af2013-02-06 01:29:3176 def Eblock(self, line=None):
[email protected]15f08dd2012-01-27 07:29:4877 """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
[email protected]32096af2013-02-06 01:29:3184 if line is not None:
85 self.Append(line)
[email protected]15f08dd2012-01-27 07:29:4886 return self
87
rdevlin.cronin8ea16e12015-03-27 00:13:1388 def Comment(self, comment, comment_prefix='// ', wrap_indent=0):
[email protected]15f08dd2012-01-27 07:29:4889 """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.
[email protected]feba21e2012-03-02 15:05:2793
94 Unaffected by code.Substitute().
[email protected]15f08dd2012-01-27 07:29:4895 """
rdevlin.cronin8ea16e12015-03-27 00:13:1396 # Helper function to trim a comment to the maximum length, and return one
97 # line and the remainder of the comment.
98 def trim_comment(comment, max_len):
99 if len(comment) <= max_len:
100 return comment, ''
101 last_space = comment.rfind(' ', 0, max_len + 1)
[email protected]15f08dd2012-01-27 07:29:48102 if last_space != -1:
rdevlin.cronin8ea16e12015-03-27 00:13:13103 line = comment[0:last_space]
[email protected]15f08dd2012-01-27 07:29:48104 comment = comment[last_space + 1:]
105 else:
rdevlin.cronin8ea16e12015-03-27 00:13:13106 line = comment[0:max_len]
[email protected]15f08dd2012-01-27 07:29:48107 comment = comment[max_len:]
rdevlin.cronin8ea16e12015-03-27 00:13:13108 return line, comment
109
110 # First line has the full maximum length.
111 max_len = self._comment_length - self._indent_level - len(comment_prefix)
112 line, comment = trim_comment(comment, max_len)
113 self.Append(comment_prefix + line, substitute=False)
114
115 # Any subsequent lines be subject to the wrap indent.
116 max_len = max_len - wrap_indent
117 while len(comment):
118 line, comment = trim_comment(comment, max_len)
119 self.Append(comment_prefix + (' ' * wrap_indent) + line, substitute=False)
120
[email protected]15f08dd2012-01-27 07:29:48121 return self
122
123 def Substitute(self, d):
124 """Goes through each line and interpolates using the given dict.
125
126 Raises type error if passed something that isn't a dict
127
128 Use for long pieces of code using interpolation with the same variables
129 repeatedly. This will reduce code and allow for named placeholders which
130 are more clear.
131 """
132 if not isinstance(d, dict):
133 raise TypeError('Passed argument is not a dictionary: ' + d)
134 for i, line in enumerate(self._code):
[email protected]feba21e2012-03-02 15:05:27135 if self._code[i].substitute:
136 # Only need to check %s because arg is a dict and python will allow
137 # '%s %(named)s' but just about nothing else
138 if '%s' in self._code[i].value or '%r' in self._code[i].value:
139 raise TypeError('"%s" or "%r" found in substitution. '
140 'Named arguments only. Use "%" to escape')
141 self._code[i].value = line.value % d
142 self._code[i].substitute = False
[email protected]15f08dd2012-01-27 07:29:48143 return self
144
145 def Render(self):
146 """Renders Code as a string.
147 """
[email protected]feba21e2012-03-02 15:05:27148 return '\n'.join([l.value for l in self._code])
[email protected]15f08dd2012-01-27 07:29:48149
[email protected]0853bcf2013-11-06 05:14:33150
[email protected]feba21e2012-03-02 15:05:27151class Line(object):
152 """A line of code.
153 """
154 def __init__(self, value, substitute=True):
155 self.value = value
156 self.substitute = substitute