Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/mako/pygen.py: 40%

141 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:02 +0000

1# mako/pygen.py 

2# Copyright 2006-2023 the Mako authors and contributors <see AUTHORS file> 

3# 

4# This module is part of Mako and is released under 

5# the MIT License: http://www.opensource.org/licenses/mit-license.php 

6 

7"""utilities for generating and formatting literal Python code.""" 

8 

9import re 

10 

11from mako import exceptions 

12 

13 

14class PythonPrinter: 

15 def __init__(self, stream): 

16 # indentation counter 

17 self.indent = 0 

18 

19 # a stack storing information about why we incremented 

20 # the indentation counter, to help us determine if we 

21 # should decrement it 

22 self.indent_detail = [] 

23 

24 # the string of whitespace multiplied by the indent 

25 # counter to produce a line 

26 self.indentstring = " " 

27 

28 # the stream we are writing to 

29 self.stream = stream 

30 

31 # current line number 

32 self.lineno = 1 

33 

34 # a list of lines that represents a buffered "block" of code, 

35 # which can be later printed relative to an indent level 

36 self.line_buffer = [] 

37 

38 self.in_indent_lines = False 

39 

40 self._reset_multi_line_flags() 

41 

42 # mapping of generated python lines to template 

43 # source lines 

44 self.source_map = {} 

45 

46 self._re_space_comment = re.compile(r"^\s*#") 

47 self._re_space = re.compile(r"^\s*$") 

48 self._re_indent = re.compile(r":[ \t]*(?:#.*)?$") 

49 self._re_compound = re.compile(r"^\s*(if|try|elif|while|for|with)") 

50 self._re_indent_keyword = re.compile( 

51 r"^\s*(def|class|else|elif|except|finally)" 

52 ) 

53 self._re_unindentor = re.compile(r"^\s*(else|elif|except|finally).*\:") 

54 

55 def _update_lineno(self, num): 

56 self.lineno += num 

57 

58 def start_source(self, lineno): 

59 if self.lineno not in self.source_map: 

60 self.source_map[self.lineno] = lineno 

61 

62 def write_blanks(self, num): 

63 self.stream.write("\n" * num) 

64 self._update_lineno(num) 

65 

66 def write_indented_block(self, block, starting_lineno=None): 

67 """print a line or lines of python which already contain indentation. 

68 

69 The indentation of the total block of lines will be adjusted to that of 

70 the current indent level.""" 

71 self.in_indent_lines = False 

72 for i, l in enumerate(re.split(r"\r?\n", block)): 

73 self.line_buffer.append(l) 

74 if starting_lineno is not None: 

75 self.start_source(starting_lineno + i) 

76 self._update_lineno(1) 

77 

78 def writelines(self, *lines): 

79 """print a series of lines of python.""" 

80 for line in lines: 

81 self.writeline(line) 

82 

83 def writeline(self, line): 

84 """print a line of python, indenting it according to the current 

85 indent level. 

86 

87 this also adjusts the indentation counter according to the 

88 content of the line. 

89 

90 """ 

91 

92 if not self.in_indent_lines: 

93 self._flush_adjusted_lines() 

94 self.in_indent_lines = True 

95 

96 if ( 

97 line is None 

98 or self._re_space_comment.match(line) 

99 or self._re_space.match(line) 

100 ): 

101 hastext = False 

102 else: 

103 hastext = True 

104 

105 is_comment = line and len(line) and line[0] == "#" 

106 

107 # see if this line should decrease the indentation level 

108 if ( 

109 not is_comment 

110 and (not hastext or self._is_unindentor(line)) 

111 and self.indent > 0 

112 ): 

113 self.indent -= 1 

114 # if the indent_detail stack is empty, the user 

115 # probably put extra closures - the resulting 

116 # module wont compile. 

117 if len(self.indent_detail) == 0: 

118 # TODO: no coverage here 

119 raise exceptions.MakoException("Too many whitespace closures") 

120 self.indent_detail.pop() 

121 

122 if line is None: 

123 return 

124 

125 # write the line 

126 self.stream.write(self._indent_line(line) + "\n") 

127 self._update_lineno(len(line.split("\n"))) 

128 

129 # see if this line should increase the indentation level. 

130 # note that a line can both decrase (before printing) and 

131 # then increase (after printing) the indentation level. 

132 

133 if self._re_indent.search(line): 

134 # increment indentation count, and also 

135 # keep track of what the keyword was that indented us, 

136 # if it is a python compound statement keyword 

137 # where we might have to look for an "unindent" keyword 

138 match = self._re_compound.match(line) 

139 if match: 

140 # its a "compound" keyword, so we will check for "unindentors" 

141 indentor = match.group(1) 

142 self.indent += 1 

143 self.indent_detail.append(indentor) 

144 else: 

145 indentor = None 

146 # its not a "compound" keyword. but lets also 

147 # test for valid Python keywords that might be indenting us, 

148 # else assume its a non-indenting line 

149 m2 = self._re_indent_keyword.match(line) 

150 if m2: 

151 self.indent += 1 

152 self.indent_detail.append(indentor) 

153 

154 def close(self): 

155 """close this printer, flushing any remaining lines.""" 

156 self._flush_adjusted_lines() 

157 

158 def _is_unindentor(self, line): 

159 """return true if the given line is an 'unindentor', 

160 relative to the last 'indent' event received. 

161 

162 """ 

163 

164 # no indentation detail has been pushed on; return False 

165 if len(self.indent_detail) == 0: 

166 return False 

167 

168 indentor = self.indent_detail[-1] 

169 

170 # the last indent keyword we grabbed is not a 

171 # compound statement keyword; return False 

172 if indentor is None: 

173 return False 

174 

175 # if the current line doesnt have one of the "unindentor" keywords, 

176 # return False 

177 match = self._re_unindentor.match(line) 

178 # if True, whitespace matches up, we have a compound indentor, 

179 # and this line has an unindentor, this 

180 # is probably good enough 

181 return bool(match) 

182 

183 # should we decide that its not good enough, heres 

184 # more stuff to check. 

185 # keyword = match.group(1) 

186 

187 # match the original indent keyword 

188 # for crit in [ 

189 # (r'if|elif', r'else|elif'), 

190 # (r'try', r'except|finally|else'), 

191 # (r'while|for', r'else'), 

192 # ]: 

193 # if re.match(crit[0], indentor) and re.match(crit[1], keyword): 

194 # return True 

195 

196 # return False 

197 

198 def _indent_line(self, line, stripspace=""): 

199 """indent the given line according to the current indent level. 

200 

201 stripspace is a string of space that will be truncated from the 

202 start of the line before indenting.""" 

203 if stripspace == "": 

204 # Fast path optimization. 

205 return self.indentstring * self.indent + line 

206 

207 return re.sub( 

208 r"^%s" % stripspace, self.indentstring * self.indent, line 

209 ) 

210 

211 def _reset_multi_line_flags(self): 

212 """reset the flags which would indicate we are in a backslashed 

213 or triple-quoted section.""" 

214 

215 self.backslashed, self.triplequoted = False, False 

216 

217 def _in_multi_line(self, line): 

218 """return true if the given line is part of a multi-line block, 

219 via backslash or triple-quote.""" 

220 

221 # we are only looking for explicitly joined lines here, not 

222 # implicit ones (i.e. brackets, braces etc.). this is just to 

223 # guard against the possibility of modifying the space inside of 

224 # a literal multiline string with unfortunately placed 

225 # whitespace 

226 

227 current_state = self.backslashed or self.triplequoted 

228 

229 self.backslashed = bool(re.search(r"\\$", line)) 

230 triples = len(re.findall(r"\"\"\"|\'\'\'", line)) 

231 if triples == 1 or triples % 2 != 0: 

232 self.triplequoted = not self.triplequoted 

233 

234 return current_state 

235 

236 def _flush_adjusted_lines(self): 

237 stripspace = None 

238 self._reset_multi_line_flags() 

239 

240 for entry in self.line_buffer: 

241 if self._in_multi_line(entry): 

242 self.stream.write(entry + "\n") 

243 else: 

244 entry = entry.expandtabs() 

245 if stripspace is None and re.search(r"^[ \t]*[^# \t]", entry): 

246 stripspace = re.match(r"^([ \t]*)", entry).group(1) 

247 self.stream.write(self._indent_line(entry, stripspace) + "\n") 

248 

249 self.line_buffer = [] 

250 self._reset_multi_line_flags() 

251 

252 

253def adjust_whitespace(text): 

254 """remove the left-whitespace margin of a block of Python code.""" 

255 

256 state = [False, False] 

257 (backslashed, triplequoted) = (0, 1) 

258 

259 def in_multi_line(line): 

260 start_state = state[backslashed] or state[triplequoted] 

261 

262 if re.search(r"\\$", line): 

263 state[backslashed] = True 

264 else: 

265 state[backslashed] = False 

266 

267 def match(reg, t): 

268 m = re.match(reg, t) 

269 if m: 

270 return m, t[len(m.group(0)) :] 

271 else: 

272 return None, t 

273 

274 while line: 

275 if state[triplequoted]: 

276 m, line = match(r"%s" % state[triplequoted], line) 

277 if m: 

278 state[triplequoted] = False 

279 else: 

280 m, line = match(r".*?(?=%s|$)" % state[triplequoted], line) 

281 else: 

282 m, line = match(r"#", line) 

283 if m: 

284 return start_state 

285 

286 m, line = match(r"\"\"\"|\'\'\'", line) 

287 if m: 

288 state[triplequoted] = m.group(0) 

289 continue 

290 

291 m, line = match(r".*?(?=\"\"\"|\'\'\'|#|$)", line) 

292 

293 return start_state 

294 

295 def _indent_line(line, stripspace=""): 

296 return re.sub(r"^%s" % stripspace, "", line) 

297 

298 lines = [] 

299 stripspace = None 

300 

301 for line in re.split(r"\r?\n", text): 

302 if in_multi_line(line): 

303 lines.append(line) 

304 else: 

305 line = line.expandtabs() 

306 if stripspace is None and re.search(r"^[ \t]*[^# \t]", line): 

307 stripspace = re.match(r"^([ \t]*)", line).group(1) 

308 lines.append(_indent_line(line, stripspace)) 

309 return "\n".join(lines)