Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -*- coding: utf-8 -*- 

2from itertools import chain 

3from typing import Iterable 

4from typing import List 

5from typing import Optional 

6from typing import Union 

7 

8from .utils import assure_str 

9 

10 

11class NestedCommentError(Exception): 

12 """Comments inside of comments are not allowed.""" 

13 

14 

15class NoCommentStartError(Exception): 

16 """Comments must be started before they are ended.""" 

17 

18 

19class CodeWriterLite: 

20 """Simple container for C code, with indentation and blocks. 

21 

22 Warning: 

23 You probably don't want to use :class:`CodeWriterLite` for code 

24 generation, but its subclass, :class:`CodeWriter`. 

25 :class:`CodeWriterLite` is meant for csnake internal use and is 

26 normally not accessible at package level. 

27 

28 CodeWriterLite is used internally in cconstructs and also as a parent class 

29 of CodeWriter. 

30 

31 CodeWriterLite (abbreviated CWL or cwl) is a container class for C code 

32 generation, which implements indentation and block ({}) opening and 

33 closing. 

34 

35 The class supports str, len, getitem, iter, and contains, making it a 

36 virtual subclass of Collection. 

37 

38 

39 Args: 

40 lf: linefeed character used to separate lines of code when 

41 rendering code with :func:`str` or :attr:`code`. 

42 indent: indentation unit string or :class:`int` number of spaces, 

43 used to indent code. 

44 

45 Attributes: 

46 

47 lf(str): linefeed character used to separate lines of code when 

48 rendering code with :func:`str` or :attr:`code`. 

49 

50 lines(list(str)): lines of code. 

51 """ 

52 

53 __slots__ = ("lf", "_indent_unit", "_indent_level", "_in_comment", "lines") 

54 

55 def __init__( 

56 self, lf: Optional[str] = None, indent: Union[int, str] = 4 

57 ) -> None: 

58 if lf is None: 

59 lf = "\n" 

60 self.lf: str = assure_str(lf) 

61 

62 if isinstance(indent, int): 

63 self._indent_unit = " " * indent 

64 else: 

65 self._indent_unit = assure_str(indent) 

66 

67 # initialize values 

68 self._indent_level = 0 

69 self._in_comment = False 

70 

71 self.lines: List[str] = [] 

72 

73 @property 

74 def _current_indent(self) -> str: 

75 return self._indent_unit * self._indent_level 

76 

77 @property 

78 def code(self) -> str: 

79 """str: The contents of the codewriter rendered to a str, using :attr:`lf` 

80 as the linebreak. 

81 """ 

82 return self.lf.join(self.lines) 

83 

84 def __str__(self) -> str: 

85 """Return the code joined by :attr:`lf`.""" 

86 return self.code 

87 

88 def __iter__(self) -> Iterable[str]: 

89 """Return an iterator that iterates over the lines in the codewriter.""" 

90 return iter(self.lines) 

91 

92 def __getitem__(self, key) -> str: 

93 """Return the nth line of code.""" 

94 return self.lines.__getitem__(key) 

95 

96 def __len__(self): 

97 """Return the number of lines in the codewriter.""" 

98 return self.lines.__len__() 

99 

100 def __contains__(self, item) -> bool: 

101 """Return whether a :class:`str` is a substring of the codewriter. 

102 

103 Args: 

104 item(str): a potential substring of the codewriter. 

105 

106 Returns: 

107 Whether or not `item` is a substring of the :func:`str` 

108 representation of the codewriter. 

109 """ 

110 return self.code.__contains__(item) 

111 

112 def indent(self) -> None: 

113 """Increase the indentation level by 1 unit. 

114 

115 Examples: 

116 :meth:`indent` and :meth:`dedent` in action: 

117 

118 >>> from csnake import CodeWriter 

119 >>> cwr = CodeWriter() 

120 >>> cwr.add_line('line_1;') 

121 >>> cwr.indent() 

122 >>> cwr.add_line('line_2;') 

123 >>> cwr.add_line('line_3;') 

124 >>> cwr.dedent() 

125 >>> cwr.add_line('line_4;') 

126 >>> print(cwr) 

127 line_1; 

128 line_2; 

129 line_3; 

130 line_4; 

131 """ 

132 self._indent_level += 1 

133 

134 def dedent(self) -> None: 

135 """Decrease the indentation level by 1 unit. 

136 

137 Examples: 

138 See :meth:`indent`. 

139 """ 

140 

141 if self._indent_level > 0: 

142 self._indent_level -= 1 

143 

144 def zero_indent(self) -> None: 

145 """Set indentation level to zero.""" 

146 self._indent_level = 0 

147 

148 def start_comment(self, ignore_indent: bool = False) -> None: 

149 """Start a comment block. 

150 

151 All of the lines added after this are in a block comment, until the 

152 block is finished by :meth:`end_comment`. 

153 

154 Args: 

155 ignore_indent: whether to ignore the indentation and just start the 

156 comment from the beginning of the next line. 

157 

158 Raises: 

159 NestedCommentError: if you attempt to start a comment block when a 

160 comment block has already been started. 

161 

162 Examples: 

163 Starting and ending a comment block with :meth:`start_comment` and 

164 :meth:`end_comment`: 

165 

166 >>> from csnake import CodeWriter 

167 >>> cwr = CodeWriter() 

168 >>> cwr.add_line('line_1;') 

169 >>> cwr.start_comment() 

170 >>> cwr.add_line('line_2;') 

171 >>> cwr.add_line('line_3;') 

172 >>> cwr.end_comment() 

173 >>> cwr.add_line('line_4;') 

174 >>> print(cwr) 

175 line_1; 

176 /* 

177 * line_2; 

178 * line_3; 

179 */ 

180 line_4; 

181 """ 

182 if self._in_comment: 

183 raise NestedCommentError 

184 self.add_line("/*", ignore_indent=ignore_indent) 

185 self._in_comment = True 

186 

187 def end_comment(self, ignore_indent: bool = False) -> None: 

188 """End a comment block. 

189 

190 A comment block previously started by :meth:`start_comment` is ended. 

191 The following code is not inside a block comment. 

192 

193 Raises: 

194 NoCommentStartError: if you attempt to end a comment block when a 

195 comment block hasn't been started. 

196 

197 Examples: 

198 See :meth:`start_comment`. 

199 """ 

200 if not self._in_comment: 

201 raise NoCommentStartError 

202 self._in_comment = False 

203 self.add_line("*/", ignore_indent=ignore_indent) 

204 

205 def open_brace(self) -> None: 

206 """Add an open brace and increase indentation for subsequent lines. 

207 

208 See :meth:`indent` for more information on how indentation is done. 

209 

210 Examples: 

211 Using :meth:`open_brace` and :meth:`close_brace`. 

212 

213 >>> from csnake import CodeWriter 

214 >>> cwr = CodeWriter() 

215 >>> cwr.add_line('line_1;') 

216 >>> cwr.open_brace() 

217 >>> cwr.add_line('line_2;') 

218 >>> cwr.add_line('line_3;') 

219 >>> cwr.close_brace() 

220 >>> cwr.add_line('line_4;') 

221 >>> print(cwr) 

222 line_1; 

223 { 

224 line_2; 

225 line_3; 

226 } 

227 line_4; 

228 """ 

229 self.add_line("{") 

230 self.indent() 

231 

232 def close_brace(self) -> None: 

233 """Add a closed brace and decrease indentation for subsequent lines. 

234 

235 See :meth:`dedent` for more information on how dedentation is done. 

236 

237 Examples: 

238 See :meth:`open_brace`. 

239 """ 

240 self.dedent() 

241 self.add_line("}") 

242 

243 def add(self, text: str) -> None: 

244 """Append text to the end of last line. 

245 

246 Args: 

247 text: :obj:`str` to append to the last line. 

248 

249 Raises: 

250 ValueError: if :obj:`text` is not a :class:`str`. 

251 """ 

252 try: 

253 self.lines[-1] 

254 except IndexError: 

255 self.lines.append("") 

256 

257 self.lines[-1] += text 

258 

259 def add_line( 

260 self, 

261 text: Optional[str] = None, 

262 comment: Optional[str] = None, 

263 ignore_indent: bool = False, 

264 ) -> None: 

265 """Add a line of text to the container after the last line. 

266 

267 Args: 

268 text: :obj:`str` to add 

269 comment: optional comment 

270 ignore_indent: switch to ignore indentation and insert the code 

271 with indentation 0 

272 

273 Raises: 

274 ValueError: if text or comment isn't a :class:`str` 

275 """ 

276 

277 # empty line 

278 if text is None and not comment and not self._in_comment: 

279 self.lines.append("") 

280 return 

281 

282 current_line = "" 

283 

284 if not ignore_indent: 

285 current_line += self._current_indent 

286 

287 if self._in_comment: 

288 current_line += "*" 

289 

290 # add the text (if appropriate) 

291 if text: 

292 text = assure_str(text) 

293 if "\n" in text: 

294 raise ValueError("text mustn't contain multiple lines.") 

295 if self._in_comment: 

296 current_line += " " 

297 current_line += text 

298 

299 if comment: 

300 comment = assure_str(comment) 

301 # add a space if there is text 

302 if text: 

303 current_line += " " 

304 if self._in_comment: 

305 current_line += f"{comment}" 

306 else: 

307 current_line += f"/* {comment} */" 

308 

309 self.lines.append(current_line) 

310 

311 def add_lines( 

312 self, lines: Union[Iterable[str], str], ignore_indent: bool = False 

313 ) -> None: 

314 """Append code lines from an iterable or a multi-line str. 

315 

316 If `lines` is an iterable iterating over :class:`str` objects, adds the 

317 lines to the codewriter in order. 

318 

319 If `lines` is a multi-line string, adds its lines to the codewriter. 

320 

321 Args: 

322 lines: an :class:`Iterable` of :class:`str` or a multi-line `str` 

323 to add to codewriter. 

324 ignore_indent: whether to ignore the current indentation level or 

325 not. If `ignore_indent` is true, the lines will be appended 

326 without indentation. 

327 

328 Raises: 

329 ValueError: if one of the arguments is of the wrong type 

330 

331 Examples: 

332 Adding a list of lines: 

333 

334 >>> from csnake import CodeWriter 

335 >>> cwr = CodeWriter() 

336 >>> lol = ['list', 'of', 'lines'] 

337 >>> cwr.add_lines(lol) 

338 >>> print(cwr) 

339 list 

340 of 

341 lines 

342 """ 

343 

344 if isinstance(lines, str): 

345 lines = lines.splitlines() 

346 else: 

347 lines = chain.from_iterable( 

348 supposed_line.splitlines() for supposed_line in lines 

349 ) 

350 

351 for line in lines: 

352 self.add_line(line, ignore_indent=ignore_indent) 

353 

354 

355assert issubclass(CodeWriterLite, Iterable)