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
_level
= 0
14 self
._indent
_size
= indent_size
15 self
._comment
_length
= comment_length
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.
21 substitute: indicated whether this line should be affected by
24 if indent_level
is None:
25 indent_level
= self
._indent
_level
26 self
._code
.append(Line(((' ' * indent_level
) + line
).rstrip(),
27 substitute
=substitute
))
31 """Returns True if the Code object is empty.
33 return not bool(self
._code
)
35 def Concat(self
, obj
):
36 """Concatenate another Code object onto this one. Trailing whitespace is
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.
43 if not isinstance(obj
, Code
):
44 raise TypeError(type(obj
))
45 assert self
is not obj
46 for line
in obj
._code
:
48 # line % () will fail if any substitution tokens are left in line
52 raise TypeError('Unsubstituted value when concatting\n' + line
.value
)
54 raise ValueError('Stray % character when concatting\n' + line
.value
)
55 self
.Append(line
.value
, line
.substitute
)
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()
66 def Sblock(self
, line
=None):
67 """Starts a code block.
69 Appends a line of code and then increases the indent level.
73 self
._indent
_level
+= self
._indent
_size
76 def Eblock(self
, line
=None):
77 """Ends a code block by decreasing and then appending a line (or a blank
80 # TODO(calamity): Decide if type checking is necessary
81 #if not isinstance(line, basestring):
83 self
._indent
_level
-= self
._indent
_size
88 def Comment(self
, comment
, comment_prefix
='// '):
89 """Adds the given string as a comment.
91 Will split the comment if it's too long. Use mainly for variable length
92 comments. Otherwise just use code.Append('// ...') for comments.
94 Unaffected by code.Substitute().
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(' ')
101 line
= line
[0:last_space
]
102 comment
= comment
[last_space
+ 1:]
104 comment
= comment
[max_len
:]
105 self
.Append(comment_prefix
+ line
, substitute
=False)
106 self
.Append(comment_prefix
+ comment
, substitute
=False)
109 def Substitute(self
, d
):
110 """Goes through each line and interpolates using the given dict.
112 Raises type error if passed something that isn't a dict
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
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
132 """Renders Code as a string.
134 return '\n'.join([l
.value
for l
in self
._code
])
140 def __init__(self
, value
, substitute
=True):
142 self
.substitute
= substitute