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.
6 """A convenience object for constructing code.
8 Logically each object should be a block of code. All methods except |Render|
9 and |IsEmpty| return self.
11 def __init__(self
, indent_size
=2, comment_length
=80):
13 self
._indent
_size
= indent_size
14 self
._comment
_length
= comment_length
15 self
._line
_prefixes
= []
17 def Append(self
, line
='',
22 """Appends a line of code at the current indent level or just a newline if
23 line is not specified.
25 substitute: indicated whether this line should be affected by
27 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.
32 prefix
= indent_level
* ' ' if indent_level
else ''.join(
38 if not new_line
and self
._code
:
39 self
._code
[-1].value
+= line
41 self
._code
.append(Line(prefix
+ line
, substitute
=substitute
))
45 """Returns True if the Code object is empty.
47 return not bool(self
._code
)
49 def Concat(self
, obj
, new_line
=True):
50 """Concatenate another Code object onto this one. Trailing whitespace is
53 Appends the code at the current indent level. Will fail if there are any
54 un-interpolated format specifiers eg %s, %(something)s which helps
55 isolate any strings that haven't been substituted.
57 if not isinstance(obj
, Code
):
58 raise TypeError(type(obj
))
59 assert self
is not obj
63 for line
in obj
._code
:
65 # line % () will fail if any substitution tokens are left in line
69 raise TypeError('Unsubstituted value when concatting\n' + line
.value
)
71 raise ValueError('Stray % character when concatting\n' + line
.value
)
72 first_line
= obj
._code
.pop(0)
73 self
.Append(first_line
.value
, first_line
.substitute
, new_line
=new_line
)
74 for line
in obj
._code
:
75 self
.Append(line
.value
, line
.substitute
)
79 def Cblock(self
, code
):
80 """Concatenates another Code object |code| onto this one followed by a
81 blank line, if |code| is non-empty."""
82 if not code
.IsEmpty():
83 self
.Concat(code
).Append()
86 def Sblock(self
, line
=None, line_prefix
=None, new_line
=True):
87 """Starts a code block.
89 Appends a line of code and then increases the indent level. If |line_prefix|
90 is present, it will be treated as the extra prefix for the code block.
91 Otherwise, the prefix will be the default indent level.
94 self
.Append(line
, new_line
=new_line
)
95 self
._line
_prefixes
.append(line_prefix
or ' ' * self
._indent
_size
)
98 def Eblock(self
, line
=None):
99 """Ends a code block by decreasing and then appending a line (or a blank
102 # TODO(calamity): Decide if type checking is necessary
103 #if not isinstance(line, basestring):
105 self
._line
_prefixes
.pop()
110 def Comment(self
, comment
, comment_prefix
='// ',
111 wrap_indent
=0, new_line
=True):
112 """Adds the given string as a comment.
114 Will split the comment if it's too long. Use mainly for variable length
115 comments. Otherwise just use code.Append('// ...') for comments.
117 Unaffected by code.Substitute().
119 # Helper function to trim a comment to the maximum length, and return one
120 # line and the remainder of the comment.
121 def trim_comment(comment
, max_len
):
122 if len(comment
) <= max_len
:
124 last_space
= comment
.rfind(' ', 0, max_len
+ 1)
126 line
= comment
[0:last_space
]
127 comment
= comment
[last_space
+ 1:]
129 line
= comment
[0:max_len
]
130 comment
= comment
[max_len
:]
133 # First line has the full maximum length.
134 if not new_line
and self
._code
:
135 max_len
= self
._comment
_length
- len(self
._code
[-1].value
)
137 max_len
= (self
._comment
_length
- len(''.join(self
._line
_prefixes
)) -
139 line
, comment
= trim_comment(comment
, max_len
)
140 self
.Append(comment_prefix
+ line
, substitute
=False, new_line
=new_line
)
142 # Any subsequent lines be subject to the wrap indent.
143 max_len
= (self
._comment
_length
- len(''.join(self
._line
_prefixes
)) -
144 len(comment_prefix
) - wrap_indent
)
146 line
, comment
= trim_comment(comment
, max_len
)
147 self
.Append(comment_prefix
+ (' ' * wrap_indent
) + line
, substitute
=False)
151 def Substitute(self
, d
):
152 """Goes through each line and interpolates using the given dict.
154 Raises type error if passed something that isn't a dict
156 Use for long pieces of code using interpolation with the same variables
157 repeatedly. This will reduce code and allow for named placeholders which
160 if not isinstance(d
, dict):
161 raise TypeError('Passed argument is not a dictionary: ' + d
)
162 for i
, line
in enumerate(self
._code
):
163 if self
._code
[i
].substitute
:
164 # Only need to check %s because arg is a dict and python will allow
165 # '%s %(named)s' but just about nothing else
166 if '%s' in self
._code
[i
].value
or '%r' in self
._code
[i
].value
:
167 raise TypeError('"%s" or "%r" found in substitution. '
168 'Named arguments only. Use "%" to escape')
169 self
._code
[i
].value
= line
.value
% d
170 self
._code
[i
].substitute
= False
174 """Renders Code as a string.
176 return '\n'.join([l
.value
for l
in self
._code
])
182 def __init__(self
, value
, substitute
=True):
184 self
.substitute
= substitute