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 Union 

6 

7from .utils import assure_str 

8 

9 

10class NestedCommentError(Exception): 

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

12 

13 

14class NoCommentStartError(Exception): 

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

16 

17 

18class CodeWriterLite: 

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

20 

21 Warning: 

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

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

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

25 normally not accessible at package level. 

26 

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

28 of CodeWriter. 

29 

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

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

32 closing. 

33 

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

35 virtual subclass of Collection. 

36 

37 

38 Args: 

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

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

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

42 used to indent code. 

43 

44 Attributes: 

45 

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

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

48 

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

50 """ 

51 

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

53 

54 def __init__(self, lf: str = None, indent: Union[int, str] = 4) -> None: 

55 

56 if lf is None: 

57 lf = "\n" 

58 self.lf: str = assure_str(lf) 

59 

60 if isinstance(indent, int): 

61 self._indent_unit = " " * indent 

62 else: 

63 self._indent_unit = assure_str(indent) 

64 

65 # initialize values 

66 self._indent_level = 0 

67 self._in_comment = False 

68 

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

70 

71 @property 

72 def _current_indent(self) -> str: 

73 return self._indent_unit * self._indent_level 

74 

75 @property 

76 def code(self) -> str: 

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

78 as the linebreak. 

79 """ 

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

81 

82 def __str__(self) -> str: 

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

84 return self.code 

85 

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

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

88 return iter(self.lines) 

89 

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

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

92 return self.lines.__getitem__(key) 

93 

94 def __len__(self): 

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

96 return self.lines.__len__() 

97 

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

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

100 

101 Args: 

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

103 

104 Returns: 

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

106 representation of the codewriter. 

107 """ 

108 return self.code.__contains__(item) 

109 

110 def indent(self) -> None: 

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

112 

113 Examples: 

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

115 

116 >>> from csnake import CodeWriter 

117 >>> cwr = CodeWriter() 

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

119 >>> cwr.indent() 

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

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

122 >>> cwr.dedent() 

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

124 >>> print(cwr) 

125 line_1; 

126 line_2; 

127 line_3; 

128 line_4; 

129 """ 

130 self._indent_level += 1 

131 

132 def dedent(self) -> None: 

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

134 

135 Examples: 

136 See :meth:`indent`. 

137 """ 

138 

139 if self._indent_level > 0: 

140 self._indent_level -= 1 

141 

142 def zero_indent(self) -> None: 

143 """Set indentation level to zero.""" 

144 self._indent_level = 0 

145 

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

147 """Start a comment block. 

148 

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

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

151 

152 Args: 

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

154 comment from the beginning of the next line. 

155 

156 Raises: 

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

158 comment block has already been started. 

159 

160 Examples: 

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

162 :meth:`end_comment`: 

163 

164 >>> from csnake import CodeWriter 

165 >>> cwr = CodeWriter() 

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

167 >>> cwr.start_comment() 

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

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

170 >>> cwr.end_comment() 

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

172 >>> print(cwr) 

173 line_1; 

174 /* 

175 * line_2; 

176 * line_3; 

177 */ 

178 line_4; 

179 """ 

180 if self._in_comment: 

181 raise NestedCommentError() 

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

183 self._in_comment = True 

184 

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

186 """End a comment block. 

187 

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

189 The following code is not inside a block comment. 

190 

191 Raises: 

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

193 comment block hasn't been started. 

194 

195 Examples: 

196 See :meth:`start_comment`. 

197 """ 

198 if not self._in_comment: 

199 raise NoCommentStartError() 

200 self._in_comment = False 

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

202 

203 def open_brace(self) -> None: 

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

205 

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

207 

208 Examples: 

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

210 

211 >>> from csnake import CodeWriter 

212 >>> cwr = CodeWriter() 

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

214 >>> cwr.open_brace() 

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

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

217 >>> cwr.close_brace() 

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

219 >>> print(cwr) 

220 line_1; 

221 { 

222 line_2; 

223 line_3; 

224 } 

225 line_4; 

226 """ 

227 self.add_line("{") 

228 self.indent() 

229 

230 def close_brace(self) -> None: 

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

232 

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

234 

235 Examples: 

236 See :meth:`open_brace`. 

237 """ 

238 self.dedent() 

239 self.add_line("}") 

240 

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

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

243 

244 Args: 

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

246 

247 Raises: 

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

249 """ 

250 try: 

251 self.lines[-1] 

252 except IndexError: 

253 self.lines.append("") 

254 

255 self.lines[-1] += text 

256 

257 def add_line( 

258 self, 

259 text: str = None, 

260 comment: str = None, 

261 ignore_indent: bool = False, 

262 ) -> None: 

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

264 

265 Args: 

266 text: :obj:`str` to add 

267 comment: optional comment 

268 ignore_indent: switch to ignore indentation and insert the code 

269 with indentation 0 

270 

271 Raises: 

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

273 """ 

274 

275 # empty line 

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

277 self.lines.append("") 

278 return 

279 

280 current_line = "" 

281 

282 if not ignore_indent: 

283 current_line += self._current_indent 

284 

285 if self._in_comment: 

286 current_line += "*" 

287 

288 # add the text (if appropriate) 

289 if text: 

290 text = assure_str(text) 

291 if "\n" in text: 

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

293 if self._in_comment: 

294 current_line += " " 

295 current_line += text 

296 

297 if comment: 

298 comment = assure_str(comment) 

299 # add a space if there is text 

300 if text: 

301 current_line += " " 

302 if self._in_comment: 

303 current_line += f"{comment}" 

304 else: 

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

306 

307 self.lines.append(current_line) 

308 

309 def add_lines( 

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

311 ) -> None: 

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

313 

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

315 lines to the codewriter in order. 

316 

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

318 

319 Args: 

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

321 to add to codewriter. 

322 ignore_indent: whether to ignore the current indentation level or 

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

324 without indentation. 

325 

326 Raises: 

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

328 

329 Examples: 

330 Adding a list of lines: 

331 

332 >>> from csnake import CodeWriter 

333 >>> cwr = CodeWriter() 

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

335 >>> cwr.add_lines(lol) 

336 >>> print(cwr) 

337 list 

338 of 

339 lines 

340 """ 

341 

342 if isinstance(lines, str): 

343 lines = lines.splitlines() 

344 else: 

345 lines = chain.from_iterable( 

346 supposed_line.splitlines() for supposed_line in lines 

347 ) 

348 

349 for line in lines: 

350 self.add_line(line, ignore_indent=ignore_indent) 

351 

352 

353assert issubclass(CodeWriterLite, Iterable)