src/eric7/QScintilla/DocstringGenerator/PyDocstringGenerator.py

branch
eric7
changeset 9221
bf71ee032bb4
parent 9209
b99e7fd55fd3
child 9237
03c714bd4ebf
equal deleted inserted replaced
9220:e9e7eca7efee 9221:bf71ee032bb4
8 """ 8 """
9 9
10 import re 10 import re
11 import collections 11 import collections
12 12
13 from .BaseDocstringGenerator import ( 13 from .BaseDocstringGenerator import BaseDocstringGenerator, FunctionInfo, getIndentStr
14 BaseDocstringGenerator, FunctionInfo, getIndentStr
15 )
16 14
17 15
18 class PyDocstringGenerator(BaseDocstringGenerator): 16 class PyDocstringGenerator(BaseDocstringGenerator):
19 """ 17 """
20 Class implementing a docstring generator for Python. 18 Class implementing a docstring generator for Python.
21 """ 19 """
20
22 def __init__(self, editor): 21 def __init__(self, editor):
23 """ 22 """
24 Constructor 23 Constructor
25 24
26 @param editor reference to the editor widget 25 @param editor reference to the editor widget
27 @type Editor 26 @type Editor
28 """ 27 """
29 super().__init__(editor) 28 super().__init__(editor)
30 29
31 self.__quote3 = '"""' 30 self.__quote3 = '"""'
32 self.__quote3Alternate = "'''" 31 self.__quote3Alternate = "'''"
33 32
34 def isFunctionStart(self, text): 33 def isFunctionStart(self, text):
35 """ 34 """
36 Public method to test, if a text is the start of a function or method 35 Public method to test, if a text is the start of a function or method
37 definition. 36 definition.
38 37
39 @param text line of text to be tested 38 @param text line of text to be tested
40 @type str 39 @type str
41 @return flag indicating that the given text starts a function or 40 @return flag indicating that the given text starts a function or
42 method definition 41 method definition
43 @rtype bool 42 @rtype bool
44 """ 43 """
45 if isinstance(text, str): 44 if isinstance(text, str):
46 text = text.lstrip() 45 text = text.lstrip()
47 if text.startswith(("def", "async def")): 46 if text.startswith(("def", "async def")):
48 return True 47 return True
49 48
50 return False 49 return False
51 50
52 def hasFunctionDefinition(self, cursorPosition): 51 def hasFunctionDefinition(self, cursorPosition):
53 """ 52 """
54 Public method to test, if the cursor is right below a function 53 Public method to test, if the cursor is right below a function
55 definition. 54 definition.
56 55
57 @param cursorPosition current cursor position (line and column) 56 @param cursorPosition current cursor position (line and column)
58 @type tuple of (int, int) 57 @type tuple of (int, int)
59 @return flag indicating cursor is right below a function definition 58 @return flag indicating cursor is right below a function definition
60 @rtype bool 59 @rtype bool
61 """ 60 """
62 return ( 61 return self.__getFunctionDefinitionFromBelow(cursorPosition) is not None
63 self.__getFunctionDefinitionFromBelow(cursorPosition) is not None 62
64 )
65
66 def isDocstringIntro(self, cursorPosition): 63 def isDocstringIntro(self, cursorPosition):
67 """ 64 """
68 Public function to test, if the line up to the cursor position might be 65 Public function to test, if the line up to the cursor position might be
69 introducing a docstring. 66 introducing a docstring.
70 67
71 @param cursorPosition current cursor position (line and column) 68 @param cursorPosition current cursor position (line and column)
72 @type tuple of (int, int) 69 @type tuple of (int, int)
73 @return flag indicating a potential start of a docstring 70 @return flag indicating a potential start of a docstring
74 @rtype bool 71 @rtype bool
75 """ 72 """
76 cline, cindex = cursorPosition 73 cline, cindex = cursorPosition
77 lineToCursor = self.editor.text(cline)[:cindex] 74 lineToCursor = self.editor.text(cline)[:cindex]
78 return self.__isTripleQuotesStart(lineToCursor) 75 return self.__isTripleQuotesStart(lineToCursor)
79 76
80 def __isTripleQuotesStart(self, text): 77 def __isTripleQuotesStart(self, text):
81 """ 78 """
82 Private method to test, if the given text is the start of a triple 79 Private method to test, if the given text is the start of a triple
83 quoted string. 80 quoted string.
84 81
85 @param text text to be inspected 82 @param text text to be inspected
86 @type str 83 @type str
87 @return flag indicating a triple quote start 84 @return flag indicating a triple quote start
88 @rtype bool 85 @rtype bool
89 """ 86 """
90 docstringTriggers = ('"""', 'r"""', "'''", "r'''") 87 docstringTriggers = ('"""', 'r"""', "'''", "r'''")
91 if text.lstrip() in docstringTriggers: 88 if text.lstrip() in docstringTriggers:
92 return True 89 return True
93 90
94 return False 91 return False
95 92
96 def insertDocstring(self, cursorPosition, fromStart=True): 93 def insertDocstring(self, cursorPosition, fromStart=True):
97 """ 94 """
98 Public method to insert a docstring for the function at the cursor 95 Public method to insert a docstring for the function at the cursor
99 position. 96 position.
100 97
101 @param cursorPosition position of the cursor (line and index) 98 @param cursorPosition position of the cursor (line and index)
102 @type tuple of (int, int) 99 @type tuple of (int, int)
103 @param fromStart flag indicating that the editor text cursor is placed 100 @param fromStart flag indicating that the editor text cursor is placed
104 on the line starting the function definition 101 on the line starting the function definition
105 @type bool 102 @type bool
106 """ 103 """
107 if fromStart: 104 if fromStart:
108 self.__functionStartLine = cursorPosition[0] 105 self.__functionStartLine = cursorPosition[0]
109 docstring, insertPos, newCursorLine = ( 106 docstring, insertPos, newCursorLine = self.__generateDocstringFromStart()
110 self.__generateDocstringFromStart() 107 else:
108 docstring, insertPos, newCursorLine = self.__generateDocstringFromBelow(
109 cursorPosition
111 ) 110 )
112 else: 111
113 docstring, insertPos, newCursorLine = (
114 self.__generateDocstringFromBelow(cursorPosition)
115 )
116
117 if docstring: 112 if docstring:
118 self.editor.beginUndoAction() 113 self.editor.beginUndoAction()
119 self.editor.insertAt(docstring, *insertPos) 114 self.editor.insertAt(docstring, *insertPos)
120 115
121 if not fromStart: 116 if not fromStart:
122 # correct triple quote indentation if neccessary 117 # correct triple quote indentation if neccessary
123 functionIndent = self.editor.indentation( 118 functionIndent = self.editor.indentation(self.__functionStartLine)
124 self.__functionStartLine)
125 quoteIndent = self.editor.indentation(insertPos[0]) 119 quoteIndent = self.editor.indentation(insertPos[0])
126 120
127 # step 1: unindent quote line until indentation is zero 121 # step 1: unindent quote line until indentation is zero
128 while quoteIndent > 0: 122 while quoteIndent > 0:
129 self.editor.unindent(insertPos[0]) 123 self.editor.unindent(insertPos[0])
130 quoteIndent = self.editor.indentation(insertPos[0]) 124 quoteIndent = self.editor.indentation(insertPos[0])
131 125
132 # step 2: indent quote line until indentation is one greater 126 # step 2: indent quote line until indentation is one greater
133 # than function definition line 127 # than function definition line
134 while quoteIndent <= functionIndent: 128 while quoteIndent <= functionIndent:
135 self.editor.indent(insertPos[0]) 129 self.editor.indent(insertPos[0])
136 quoteIndent = self.editor.indentation(insertPos[0]) 130 quoteIndent = self.editor.indentation(insertPos[0])
137 131
138 self.editor.endUndoAction() 132 self.editor.endUndoAction()
139 self.editor.setCursorPosition( 133 self.editor.setCursorPosition(
140 newCursorLine, len(self.editor.text(newCursorLine)) - 1 134 newCursorLine, len(self.editor.text(newCursorLine)) - 1
141 ) 135 )
142 136
143 def insertDocstringFromShortcut(self, cursorPosition): 137 def insertDocstringFromShortcut(self, cursorPosition):
144 """ 138 """
145 Public method to insert a docstring for the function at the cursor 139 Public method to insert a docstring for the function at the cursor
146 position initiated via a keyboard shortcut. 140 position initiated via a keyboard shortcut.
147 141
148 @param cursorPosition position of the cursor (line and index) 142 @param cursorPosition position of the cursor (line and index)
149 @type tuple of (int, int) 143 @type tuple of (int, int)
150 """ 144 """
151 result = self.__getFunctionDefinitionFromBelow(cursorPosition) 145 result = self.__getFunctionDefinitionFromBelow(cursorPosition)
152 if result is not None: 146 if result is not None:
160 self.__functionStartLine = cursorPosition[0] 154 self.__functionStartLine = cursorPosition[0]
161 else: 155 else:
162 # neither after the function definition nor at the start 156 # neither after the function definition nor at the start
163 # just do nothing 157 # just do nothing
164 return 158 return
165 159
166 docstring, insertPos, newCursorLine = ( 160 docstring, insertPos, newCursorLine = self.__generateDocstringFromStart()
167 self.__generateDocstringFromStart()
168 )
169 if docstring: 161 if docstring:
170 self.editor.beginUndoAction() 162 self.editor.beginUndoAction()
171 self.editor.insertAt(docstring, *insertPos) 163 self.editor.insertAt(docstring, *insertPos)
172 self.editor.endUndoAction() 164 self.editor.endUndoAction()
173 self.editor.setCursorPosition( 165 self.editor.setCursorPosition(
174 newCursorLine, len(self.editor.text(newCursorLine)) - 1 166 newCursorLine, len(self.editor.text(newCursorLine)) - 1
175 ) 167 )
176 168
177 def __getIndentationInsertString(self, text): 169 def __getIndentationInsertString(self, text):
178 """ 170 """
179 Private method to create the indentation string for the docstring. 171 Private method to create the indentation string for the docstring.
180 172
181 @param text text to based the indentation on 173 @param text text to based the indentation on
182 @type str 174 @type str
183 @return indentation string for docstring 175 @return indentation string for docstring
184 @rtype str 176 @rtype str
185 """ 177 """
186 indent = getIndentStr(text) 178 indent = getIndentStr(text)
187 indentWidth = self.editor.indentationWidth() 179 indentWidth = self.editor.indentationWidth()
188 if indentWidth == 0: 180 if indentWidth == 0:
189 indentWidth = self.editor.tabWidth() 181 indentWidth = self.editor.tabWidth()
190 182
191 return indent + indentWidth * " " 183 return indent + indentWidth * " "
192 184
193 ####################################################################### 185 #######################################################################
194 ## Methods to generate the docstring when the text cursor is on the 186 ## Methods to generate the docstring when the text cursor is on the
195 ## line starting the function definition. 187 ## line starting the function definition.
196 ####################################################################### 188 #######################################################################
197 189
198 def __generateDocstringFromStart(self): 190 def __generateDocstringFromStart(self):
199 """ 191 """
200 Private method to generate a docstring based on the cursor being 192 Private method to generate a docstring based on the cursor being
201 placed on the first line of the definition. 193 placed on the first line of the definition.
202 194
203 @return tuple containing the docstring and a tuple containing the 195 @return tuple containing the docstring and a tuple containing the
204 insertion line and index 196 insertion line and index
205 @rtype tuple of (str, tuple(int, int)) 197 @rtype tuple of (str, tuple(int, int))
206 """ 198 """
207 result = self.__getFunctionDefinitionFromStart() 199 result = self.__getFunctionDefinitionFromStart()
208 if result: 200 if result:
209 functionDefinition, functionDefinitionLength = result 201 functionDefinition, functionDefinitionLength = result
210 202
211 insertLine = self.__functionStartLine + functionDefinitionLength 203 insertLine = self.__functionStartLine + functionDefinitionLength
212 indentation = self.__getIndentationInsertString(functionDefinition) 204 indentation = self.__getIndentationInsertString(functionDefinition)
213 sep = self.editor.getLineSeparator() 205 sep = self.editor.getLineSeparator()
214 bodyStart = insertLine 206 bodyStart = insertLine
215 207
216 docstringList = self.__generateDocstring( 208 docstringList = self.__generateDocstring('"', functionDefinition, bodyStart)
217 '"', functionDefinition, bodyStart
218 )
219 if docstringList: 209 if docstringList:
220 if self.getDocstringType() == "ericdoc": 210 if self.getDocstringType() == "ericdoc":
221 docstringList.insert(0, self.__quote3) 211 docstringList.insert(0, self.__quote3)
222 newCursorLine = insertLine + 1 212 newCursorLine = insertLine + 1
223 else: 213 else:
224 docstringList[0] = self.__quote3 + docstringList[0] 214 docstringList[0] = self.__quote3 + docstringList[0]
225 newCursorLine = insertLine 215 newCursorLine = insertLine
226 docstringList.append(self.__quote3) 216 docstringList.append(self.__quote3)
227 return ( 217 return (
228 indentation + 218 (
229 "{0}{1}".format(sep, indentation).join(docstringList) + 219 indentation
230 sep 220 + "{0}{1}".format(sep, indentation).join(docstringList)
231 ), (insertLine, 0), newCursorLine 221 + sep
232 222 ),
223 (insertLine, 0),
224 newCursorLine,
225 )
226
233 return "", (0, 0), 0 227 return "", (0, 0), 0
234 228
235 def __getFunctionDefinitionFromStart(self): 229 def __getFunctionDefinitionFromStart(self):
236 """ 230 """
237 Private method to extract the function definition based on the cursor 231 Private method to extract the function definition based on the cursor
238 being placed on the first line of the definition. 232 being placed on the first line of the definition.
239 233
240 @return text containing the function definition 234 @return text containing the function definition
241 @rtype str 235 @rtype str
242 """ 236 """
243 startLine = self.__functionStartLine 237 startLine = self.__functionStartLine
244 endLine = startLine + min( 238 endLine = startLine + min(
245 self.editor.lines() - startLine, 239 self.editor.lines() - startLine, 20 # max. 20 lines of definition allowed
246 20 # max. 20 lines of definition allowed
247 ) 240 )
248 isFirstLine = True 241 isFirstLine = True
249 functionIndent = "" 242 functionIndent = ""
250 functionTextList = [] 243 functionTextList = []
251 244
252 for lineNo in range(startLine, endLine): 245 for lineNo in range(startLine, endLine):
253 text = self.editor.text(lineNo).rstrip() 246 text = self.editor.text(lineNo).rstrip()
254 if isFirstLine: 247 if isFirstLine:
255 if not self.isFunctionStart(text): 248 if not self.isFunctionStart(text):
256 return None 249 return None
257 250
258 functionIndent = getIndentStr(text) 251 functionIndent = getIndentStr(text)
259 isFirstLine = False 252 isFirstLine = False
260 else: 253 else:
261 currentIndent = getIndentStr(text) 254 currentIndent = getIndentStr(text)
262 if ( 255 if currentIndent <= functionIndent or self.isFunctionStart(text):
263 currentIndent <= functionIndent or
264 self.isFunctionStart(text)
265 ):
266 # no function body exists 256 # no function body exists
267 return None 257 return None
268 if text.strip() == "": 258 if text.strip() == "":
269 # empty line, illegal/incomplete function definition 259 # empty line, illegal/incomplete function definition
270 return None 260 return None
271 261
272 if text.endswith("\\"): 262 if text.endswith("\\"):
273 text = text[:-1] 263 text = text[:-1]
274 264
275 functionTextList.append(text) 265 functionTextList.append(text)
276 266
277 if text.endswith(":"): 267 if text.endswith(":"):
278 # end of function definition reached 268 # end of function definition reached
279 functionDefinitionLength = len(functionTextList) 269 functionDefinitionLength = len(functionTextList)
280 270
281 # check, if function is decorated with a supported one 271 # check, if function is decorated with a supported one
282 if startLine > 0: 272 if startLine > 0:
283 decoratorLine = self.editor.text(startLine - 1) 273 decoratorLine = self.editor.text(startLine - 1)
284 if ( 274 if (
285 "@classmethod" in decoratorLine or 275 "@classmethod" in decoratorLine
286 "@staticmethod" in decoratorLine or 276 or "@staticmethod" in decoratorLine
287 "pyqtSlot" in decoratorLine or # PyQt slot 277 or "pyqtSlot" in decoratorLine
288 "Slot" in decoratorLine # PySide slot 278 or "Slot" in decoratorLine # PyQt slot # PySide slot
289 ): 279 ):
290 functionTextList.insert(0, decoratorLine) 280 functionTextList.insert(0, decoratorLine)
291 281
292 return "".join(functionTextList), functionDefinitionLength 282 return "".join(functionTextList), functionDefinitionLength
293 283
294 return None 284 return None
295 285
296 ####################################################################### 286 #######################################################################
297 ## Methods to generate the docstring when the text cursor is on the 287 ## Methods to generate the docstring when the text cursor is on the
298 ## line after the function definition (e.g. after a triple quote). 288 ## line after the function definition (e.g. after a triple quote).
299 ####################################################################### 289 #######################################################################
300 290
301 def __generateDocstringFromBelow(self, cursorPosition): 291 def __generateDocstringFromBelow(self, cursorPosition):
302 """ 292 """
303 Private method to generate a docstring when the gicen position is on 293 Private method to generate a docstring when the gicen position is on
304 the line below the end of the definition. 294 the line below the end of the definition.
305 295
306 @param cursorPosition position of the cursor (line and index) 296 @param cursorPosition position of the cursor (line and index)
307 @type tuple of (int, int) 297 @type tuple of (int, int)
308 @return tuple containing the docstring and a tuple containing the 298 @return tuple containing the docstring and a tuple containing the
309 insertion line and index 299 insertion line and index
310 @rtype tuple of (str, tuple(int, int)) 300 @rtype tuple of (str, tuple(int, int))
311 """ 301 """
312 functionDefinition = self.__getFunctionDefinitionFromBelow( 302 functionDefinition = self.__getFunctionDefinitionFromBelow(cursorPosition)
313 cursorPosition)
314 if functionDefinition: 303 if functionDefinition:
315 lineTextToCursor = ( 304 lineTextToCursor = self.editor.text(cursorPosition[0])[: cursorPosition[1]]
316 self.editor.text(cursorPosition[0])[:cursorPosition[1]]
317 )
318 insertLine = cursorPosition[0] 305 insertLine = cursorPosition[0]
319 indentation = self.__getIndentationInsertString(functionDefinition) 306 indentation = self.__getIndentationInsertString(functionDefinition)
320 sep = self.editor.getLineSeparator() 307 sep = self.editor.getLineSeparator()
321 bodyStart = insertLine 308 bodyStart = insertLine
322 309
323 docstringList = self.__generateDocstring( 310 docstringList = self.__generateDocstring('"', functionDefinition, bodyStart)
324 '"', functionDefinition, bodyStart
325 )
326 if docstringList: 311 if docstringList:
327 if self.__isTripleQuotesStart(lineTextToCursor): 312 if self.__isTripleQuotesStart(lineTextToCursor):
328 if self.getDocstringType() == "ericdoc": 313 if self.getDocstringType() == "ericdoc":
329 docstringList.insert(0, "") 314 docstringList.insert(0, "")
330 newCursorLine = cursorPosition[0] + 1 315 newCursorLine = cursorPosition[0] + 1
337 newCursorLine = cursorPosition[0] + 1 322 newCursorLine = cursorPosition[0] + 1
338 else: 323 else:
339 docstringList[0] = self.__quote3 + docstringList[0] 324 docstringList[0] = self.__quote3 + docstringList[0]
340 newCursorLine = cursorPosition[0] 325 newCursorLine = cursorPosition[0]
341 docstringList.append(self.__quote3) 326 docstringList.append(self.__quote3)
342 docstring = ( 327 docstring = "{0}{1}".format(sep, indentation).join(docstringList)
343 "{0}{1}".format(sep, indentation).join(docstringList)
344 )
345 return docstring, cursorPosition, newCursorLine 328 return docstring, cursorPosition, newCursorLine
346 329
347 return "", (0, 0), 0 330 return "", (0, 0), 0
348 331
349 def __getFunctionDefinitionFromBelow(self, cursorPosition): 332 def __getFunctionDefinitionFromBelow(self, cursorPosition):
350 """ 333 """
351 Private method to extract the function definition based on the cursor 334 Private method to extract the function definition based on the cursor
352 being placed on the first line after the definition. 335 being placed on the first line after the definition.
353 336
354 @param cursorPosition current cursor position (line and column) 337 @param cursorPosition current cursor position (line and column)
355 @type tuple of (int, int) 338 @type tuple of (int, int)
356 @return text containing the function definition 339 @return text containing the function definition
357 @rtype str 340 @rtype str
358 """ 341 """
359 startLine = cursorPosition[0] - 1 342 startLine = cursorPosition[0] - 1
360 endLine = startLine - min(startLine, 20) 343 endLine = startLine - min(startLine, 20)
361 # max. 20 lines of definition allowed 344 # max. 20 lines of definition allowed
362 isFirstLine = True 345 isFirstLine = True
363 functionTextList = [] 346 functionTextList = []
364 347
365 for lineNo in range(startLine, endLine, -1): 348 for lineNo in range(startLine, endLine, -1):
366 text = self.editor.text(lineNo).rstrip() 349 text = self.editor.text(lineNo).rstrip()
367 if isFirstLine: 350 if isFirstLine:
368 if not text.endswith(":"): 351 if not text.endswith(":"):
369 return None 352 return None
370 isFirstLine = False 353 isFirstLine = False
371 elif text.endswith(":") or text == "": 354 elif text.endswith(":") or text == "":
372 return None 355 return None
373 356
374 if text.endswith("\\"): 357 if text.endswith("\\"):
375 text = text[:-1] 358 text = text[:-1]
376 359
377 functionTextList.insert(0, text) 360 functionTextList.insert(0, text)
378 361
379 if self.isFunctionStart(text): 362 if self.isFunctionStart(text):
380 # start of function definition reached 363 # start of function definition reached
381 self.__functionStartLine = lineNo 364 self.__functionStartLine = lineNo
382 365
383 # check, if function is decorated with a supported one 366 # check, if function is decorated with a supported one
384 if lineNo > 0: 367 if lineNo > 0:
385 decoratorLine = self.editor.text(lineNo - 1) 368 decoratorLine = self.editor.text(lineNo - 1)
386 if ( 369 if (
387 "@classmethod" in decoratorLine or 370 "@classmethod" in decoratorLine
388 "@staticmethod" in decoratorLine or 371 or "@staticmethod" in decoratorLine
389 "pyqtSlot" in decoratorLine or # PyQt slot 372 or "pyqtSlot" in decoratorLine
390 "Slot" in decoratorLine # PySide slot 373 or "Slot" in decoratorLine # PyQt slot # PySide slot
391 ): 374 ):
392 functionTextList.insert(0, decoratorLine) 375 functionTextList.insert(0, decoratorLine)
393 376
394 return "".join(functionTextList) 377 return "".join(functionTextList)
395 378
396 return None 379 return None
397 380
398 ####################################################################### 381 #######################################################################
399 ## Methods to generate the docstring contents. 382 ## Methods to generate the docstring contents.
400 ####################################################################### 383 #######################################################################
401 384
402 def __getFunctionBody(self, functionIndent, startLine): 385 def __getFunctionBody(self, functionIndent, startLine):
403 """ 386 """
404 Private method to get the function body. 387 Private method to get the function body.
405 388
406 @param functionIndent indentation string of the function definition 389 @param functionIndent indentation string of the function definition
407 @type str 390 @type str
408 @param startLine starting line for the extraction process 391 @param startLine starting line for the extraction process
409 @type int 392 @type int
410 @return text containing the function body 393 @return text containing the function body
411 @rtype str 394 @rtype str
412 """ 395 """
413 bodyList = [] 396 bodyList = []
414 397
415 for line in range(startLine, self.editor.lines()): 398 for line in range(startLine, self.editor.lines()):
416 text = self.editor.text(line) 399 text = self.editor.text(line)
417 textIndent = getIndentStr(text) 400 textIndent = getIndentStr(text)
418 401
419 if text.strip() == "": 402 if text.strip() == "":
420 pass 403 pass
421 elif len(textIndent) <= len(functionIndent): 404 elif len(textIndent) <= len(functionIndent):
422 break 405 break
423 406
424 bodyList.append(text) 407 bodyList.append(text)
425 408
426 return "".join(bodyList) 409 return "".join(bodyList)
427 410
428 def __generateDocstring(self, quote, functionDef, bodyStartLine): 411 def __generateDocstring(self, quote, functionDef, bodyStartLine):
429 """ 412 """
430 Private method to generate the list of docstring lines. 413 Private method to generate the list of docstring lines.
431 414
432 @param quote quote string 415 @param quote quote string
433 @type str 416 @type str
434 @param functionDef text containing the function definition 417 @param functionDef text containing the function definition
435 @type str 418 @type str
436 @param bodyStartLine starting line of the function body 419 @param bodyStartLine starting line of the function body
443 quote3replace = 3 * "'" 426 quote3replace = 3 * "'"
444 elif quote == "'": 427 elif quote == "'":
445 quote3replace = 3 * '"' 428 quote3replace = 3 * '"'
446 functionInfo = PyFunctionInfo() 429 functionInfo = PyFunctionInfo()
447 functionInfo.parseDefinition(functionDef, quote3, quote3replace) 430 functionInfo.parseDefinition(functionDef, quote3, quote3replace)
448 431
449 if functionInfo.hasInfo: 432 if functionInfo.hasInfo:
450 functionBody = self.__getFunctionBody(functionInfo.functionIndent, 433 functionBody = self.__getFunctionBody(
451 bodyStartLine) 434 functionInfo.functionIndent, bodyStartLine
452 435 )
436
453 if functionBody: 437 if functionBody:
454 functionInfo.parseBody(functionBody) 438 functionInfo.parseBody(functionBody)
455 439
456 docstringType = self.getDocstringType() 440 docstringType = self.getDocstringType()
457 return self._generateDocstringList(functionInfo, docstringType) 441 return self._generateDocstringList(functionInfo, docstringType)
458 442
459 return [] 443 return []
460 444
461 445
462 class PyFunctionInfo(FunctionInfo): 446 class PyFunctionInfo(FunctionInfo):
463 """ 447 """
464 Class implementing an object to extract and store function information. 448 Class implementing an object to extract and store function information.
465 """ 449 """
450
466 def __init__(self): 451 def __init__(self):
467 """ 452 """
468 Constructor 453 Constructor
469 """ 454 """
470 super().__init__() 455 super().__init__()
471 456
472 def __isCharInPairs(self, posChar, pairs): 457 def __isCharInPairs(self, posChar, pairs):
473 """ 458 """
474 Private method to test, if the given character position is between 459 Private method to test, if the given character position is between
475 pairs of brackets or quotes. 460 pairs of brackets or quotes.
476 461
477 @param posChar character position to be tested 462 @param posChar character position to be tested
478 @type int 463 @type int
479 @param pairs list containing pairs of positions 464 @param pairs list containing pairs of positions
480 @type list of tuple of (int, int) 465 @type list of tuple of (int, int)
481 @return flag indicating the position is in between 466 @return flag indicating the position is in between
482 @rtype bool 467 @rtype bool
483 """ 468 """
484 return any(posLeft < posChar < posRight 469 return any(posLeft < posChar < posRight for (posLeft, posRight) in pairs)
485 for (posLeft, posRight) in pairs) 470
486
487 def __findQuotePosition(self, text): 471 def __findQuotePosition(self, text):
488 """ 472 """
489 Private method to find the start and end position of pairs of quotes. 473 Private method to find the start and end position of pairs of quotes.
490 474
491 @param text text to be parsed 475 @param text text to be parsed
492 @type str 476 @type str
493 @return list of tuple with start and end position of pairs of quotes 477 @return list of tuple with start and end position of pairs of quotes
494 @rtype list of tuple of (int, int) 478 @rtype list of tuple of (int, int)
495 @exception IndexError raised when a matching close quote is missing 479 @exception IndexError raised when a matching close quote is missing
496 """ 480 """
497 pos = [] 481 pos = []
498 foundLeftQuote = False 482 foundLeftQuote = False
499 483
500 for index, character in enumerate(text): 484 for index, character in enumerate(text):
501 if foundLeftQuote is False: 485 if foundLeftQuote is False:
502 if character in ("'", '"'): 486 if character in ("'", '"'):
503 foundLeftQuote = True 487 foundLeftQuote = True
504 quote = character 488 quote = character
505 leftPos = index 489 leftPos = index
506 else: 490 else:
507 if character == quote and text[index - 1] != "\\": 491 if character == quote and text[index - 1] != "\\":
508 pos.append((leftPos, index)) 492 pos.append((leftPos, index))
509 foundLeftQuote = False 493 foundLeftQuote = False
510 494
511 if foundLeftQuote: 495 if foundLeftQuote:
512 raise IndexError("No matching close quote at: {0}".format(leftPos)) 496 raise IndexError("No matching close quote at: {0}".format(leftPos))
513 497
514 return pos 498 return pos
515 499
516 def __findBracketPosition(self, text, bracketLeft, bracketRight, posQuote): 500 def __findBracketPosition(self, text, bracketLeft, bracketRight, posQuote):
517 """ 501 """
518 Private method to find the start and end position of pairs of brackets. 502 Private method to find the start and end position of pairs of brackets.
519 503
520 https://stackoverflow.com/questions/29991917/ 504 https://stackoverflow.com/questions/29991917/
521 indices-of-matching-parentheses-in-python 505 indices-of-matching-parentheses-in-python
522 506
523 @param text text to be parsed 507 @param text text to be parsed
524 @type str 508 @type str
525 @param bracketLeft character of the left bracket 509 @param bracketLeft character of the left bracket
526 @type str 510 @type str
527 @param bracketRight character of the right bracket 511 @param bracketRight character of the right bracket
534 @exception IndexError raised when a closing or opening bracket is 518 @exception IndexError raised when a closing or opening bracket is
535 missing 519 missing
536 """ 520 """
537 pos = [] 521 pos = []
538 pstack = [] 522 pstack = []
539 523
540 for index, character in enumerate(text): 524 for index, character in enumerate(text):
541 if ( 525 if character == bracketLeft and not self.__isCharInPairs(index, posQuote):
542 character == bracketLeft and
543 not self.__isCharInPairs(index, posQuote)
544 ):
545 pstack.append(index) 526 pstack.append(index)
546 elif ( 527 elif character == bracketRight and not self.__isCharInPairs(
547 character == bracketRight and 528 index, posQuote
548 not self.__isCharInPairs(index, posQuote)
549 ): 529 ):
550 if len(pstack) == 0: 530 if len(pstack) == 0:
551 raise IndexError( 531 raise IndexError("No matching closing parens at: {0}".format(index))
552 "No matching closing parens at: {0}".format(index))
553 pos.append((pstack.pop(), index)) 532 pos.append((pstack.pop(), index))
554 533
555 if len(pstack) > 0: 534 if len(pstack) > 0:
556 raise IndexError( 535 raise IndexError("No matching opening parens at: {0}".format(pstack.pop()))
557 "No matching opening parens at: {0}".format(pstack.pop())) 536
558
559 return pos 537 return pos
560 538
561 def __splitArgumentToNameTypeValue(self, argumentsList, 539 def __splitArgumentToNameTypeValue(self, argumentsList, quote, quoteReplace):
562 quote, quoteReplace):
563 """ 540 """
564 Private method to split some argument text to name, type and value. 541 Private method to split some argument text to name, type and value.
565 542
566 @param argumentsList list of function argument definitions 543 @param argumentsList list of function argument definitions
567 @type list of str 544 @type list of str
568 @param quote quote string to be replaced 545 @param quote quote string to be replaced
569 @type str 546 @type str
570 @param quoteReplace quote string to replace the original 547 @param quoteReplace quote string to replace the original
571 @type str 548 @type str
572 """ 549 """
573 for arg in argumentsList: 550 for arg in argumentsList:
574 hasType = False 551 hasType = False
575 hasValue = False 552 hasValue = False
576 553
577 colonPosition = arg.find(":") 554 colonPosition = arg.find(":")
578 equalPosition = arg.find("=") 555 equalPosition = arg.find("=")
579 556
580 if equalPosition > -1: 557 if equalPosition > -1:
581 hasValue = True 558 hasValue = True
582 559
583 if ( 560 if colonPosition > -1 and (not hasValue or equalPosition > colonPosition):
584 colonPosition > -1 and
585 (not hasValue or equalPosition > colonPosition)
586 ):
587 # exception for def foo(arg1=":") 561 # exception for def foo(arg1=":")
588 hasType = True 562 hasType = True
589 563
590 if hasValue and hasType: 564 if hasValue and hasType:
591 argName = arg[0:colonPosition].strip() 565 argName = arg[0:colonPosition].strip()
592 argType = arg[colonPosition + 1:equalPosition].strip() 566 argType = arg[colonPosition + 1 : equalPosition].strip()
593 argValue = arg[equalPosition + 1:].strip() 567 argValue = arg[equalPosition + 1 :].strip()
594 elif not hasValue and hasType: 568 elif not hasValue and hasType:
595 argName = arg[0:colonPosition].strip() 569 argName = arg[0:colonPosition].strip()
596 argType = arg[colonPosition + 1:].strip() 570 argType = arg[colonPosition + 1 :].strip()
597 argValue = None 571 argValue = None
598 elif hasValue and not hasType: 572 elif hasValue and not hasType:
599 argName = arg[0:equalPosition].strip() 573 argName = arg[0:equalPosition].strip()
600 argType = None 574 argType = None
601 argValue = arg[equalPosition + 1:].strip() 575 argValue = arg[equalPosition + 1 :].strip()
602 else: 576 else:
603 argName = arg.strip() 577 argName = arg.strip()
604 argType = None 578 argType = None
605 argValue = None 579 argValue = None
606 if argValue and quote: 580 if argValue and quote:
607 # sanitize argValue with respect to quotes 581 # sanitize argValue with respect to quotes
608 argValue = argValue.replace(quote, quoteReplace) 582 argValue = argValue.replace(quote, quoteReplace)
609 583
610 self.argumentsList.append((argName, argType, argValue)) 584 self.argumentsList.append((argName, argType, argValue))
611 585
612 def __splitArgumentsTextToList(self, argumentsText): 586 def __splitArgumentsTextToList(self, argumentsText):
613 """ 587 """
614 Private method to split the given arguments text into a list of 588 Private method to split the given arguments text into a list of
615 arguments. 589 arguments.
616 590
617 This function uses a comma to separate arguments and ignores a comma in 591 This function uses a comma to separate arguments and ignores a comma in
618 brackets and quotes. 592 brackets and quotes.
619 593
620 @param argumentsText text containing the list of arguments 594 @param argumentsText text containing the list of arguments
621 @type str 595 @type str
622 @return list of individual argument texts 596 @return list of individual argument texts
623 @rtype list of str 597 @rtype list of str
624 """ 598 """
625 argumentsList = [] 599 argumentsList = []
626 indexFindStart = 0 600 indexFindStart = 0
627 indexArgStart = 0 601 indexArgStart = 0
628 602
629 try: 603 try:
630 posQuote = self.__findQuotePosition(argumentsText) 604 posQuote = self.__findQuotePosition(argumentsText)
631 posRound = self.__findBracketPosition( 605 posRound = self.__findBracketPosition(argumentsText, "(", ")", posQuote)
632 argumentsText, "(", ")", posQuote) 606 posCurly = self.__findBracketPosition(argumentsText, "{", "}", posQuote)
633 posCurly = self.__findBracketPosition( 607 posSquare = self.__findBracketPosition(argumentsText, "[", "]", posQuote)
634 argumentsText, "{", "}", posQuote)
635 posSquare = self.__findBracketPosition(
636 argumentsText, "[", "]", posQuote)
637 except IndexError: 608 except IndexError:
638 return None 609 return None
639 610
640 while True: 611 while True:
641 posComma = argumentsText.find(",", indexFindStart) 612 posComma = argumentsText.find(",", indexFindStart)
642 613
643 if posComma == -1: 614 if posComma == -1:
644 break 615 break
645 616
646 indexFindStart = posComma + 1 617 indexFindStart = posComma + 1
647 618
648 if ( 619 if (
649 self.__isCharInPairs(posComma, posRound) or 620 self.__isCharInPairs(posComma, posRound)
650 self.__isCharInPairs(posComma, posCurly) or 621 or self.__isCharInPairs(posComma, posCurly)
651 self.__isCharInPairs(posComma, posSquare) or 622 or self.__isCharInPairs(posComma, posSquare)
652 self.__isCharInPairs(posComma, posQuote) 623 or self.__isCharInPairs(posComma, posQuote)
653 ): 624 ):
654 continue 625 continue
655 626
656 argumentsList.append(argumentsText[indexArgStart:posComma]) 627 argumentsList.append(argumentsText[indexArgStart:posComma])
657 indexArgStart = posComma + 1 628 indexArgStart = posComma + 1
658 629
659 if indexArgStart < len(argumentsText): 630 if indexArgStart < len(argumentsText):
660 argumentsList.append(argumentsText[indexArgStart:]) 631 argumentsList.append(argumentsText[indexArgStart:])
661 632
662 return argumentsList 633 return argumentsList
663 634
664 def parseDefinition(self, text, quote, quoteReplace): 635 def parseDefinition(self, text, quote, quoteReplace):
665 """ 636 """
666 Public method to parse the function definition text. 637 Public method to parse the function definition text.
667 638
668 @param text text containing the function definition 639 @param text text containing the function definition
669 @type str 640 @type str
670 @param quote quote string to be replaced 641 @param quote quote string to be replaced
671 @type str 642 @type str
672 @param quoteReplace quote string to replace the original 643 @param quoteReplace quote string to replace the original
673 @type str 644 @type str
674 """ 645 """
675 self.functionIndent = getIndentStr(text) 646 self.functionIndent = getIndentStr(text)
676 647
677 textList = text.splitlines() 648 textList = text.splitlines()
678 if textList[0].lstrip().startswith("@"): 649 if textList[0].lstrip().startswith("@"):
679 # first line of function definition is a decorator 650 # first line of function definition is a decorator
680 decorator = textList.pop(0).strip() 651 decorator = textList.pop(0).strip()
681 if decorator == "@staticmethod": 652 if decorator == "@staticmethod":
682 self.functionType = "staticmethod" 653 self.functionType = "staticmethod"
683 elif decorator == "@classmethod": 654 elif decorator == "@classmethod":
684 self.functionType = "classmethod" 655 self.functionType = "classmethod"
685 elif ( 656 elif re.match(r"@(PyQt[456]\.)?(QtCore\.)?pyqtSlot", decorator) or re.match(
686 re.match(r"@(PyQt[456]\.)?(QtCore\.)?pyqtSlot", decorator) or 657 r"@(PySide[26]\.)?(QtCore\.)?Slot", decorator
687 re.match(r"@(PySide[26]\.)?(QtCore\.)?Slot", decorator)
688 ): 658 ):
689 self.functionType = "qtslot" 659 self.functionType = "qtslot"
690 660
691 text = "".join(textList).strip() 661 text = "".join(textList).strip()
692 662
693 if text.startswith("async def "): 663 if text.startswith("async def "):
694 self.isAsync = True 664 self.isAsync = True
695 665
696 returnType = re.search(r"->[ ]*([a-zA-Z0-9_,()\[\] ]*):$", text) 666 returnType = re.search(r"->[ ]*([a-zA-Z0-9_,()\[\] ]*):$", text)
697 if returnType: 667 if returnType:
698 self.returnTypeAnnotated = returnType.group(1) 668 self.returnTypeAnnotated = returnType.group(1)
699 textEnd = text.rfind(returnType.group(0)) 669 textEnd = text.rfind(returnType.group(0))
700 else: 670 else:
701 self.returnTypeAnnotated = None 671 self.returnTypeAnnotated = None
702 textEnd = len(text) 672 textEnd = len(text)
703 673
704 positionArgumentsStart = text.find("(") + 1 674 positionArgumentsStart = text.find("(") + 1
705 positionArgumentsEnd = text.rfind(")", positionArgumentsStart, 675 positionArgumentsEnd = text.rfind(")", positionArgumentsStart, textEnd)
706 textEnd) 676
707
708 self.argumentsText = text[positionArgumentsStart:positionArgumentsEnd] 677 self.argumentsText = text[positionArgumentsStart:positionArgumentsEnd]
709 678
710 argumentsList = self.__splitArgumentsTextToList(self.argumentsText) 679 argumentsList = self.__splitArgumentsTextToList(self.argumentsText)
711 if argumentsList is not None: 680 if argumentsList is not None:
712 self.hasInfo = True 681 self.hasInfo = True
713 self.__splitArgumentToNameTypeValue( 682 self.__splitArgumentToNameTypeValue(argumentsList, quote, quoteReplace)
714 argumentsList, quote, quoteReplace) 683
715
716 functionName = ( 684 functionName = (
717 text[:positionArgumentsStart - 1] 685 text[: positionArgumentsStart - 1]
718 .replace("async def ", "") 686 .replace("async def ", "")
719 .replace("def ", "") 687 .replace("def ", "")
720 ) 688 )
721 if functionName == "__init__": 689 if functionName == "__init__":
722 self.functionType = "constructor" 690 self.functionType = "constructor"
727 self.visibility = "private" 695 self.visibility = "private"
728 elif functionName.startswith("_"): 696 elif functionName.startswith("_"):
729 self.visibility = "protected" 697 self.visibility = "protected"
730 else: 698 else:
731 self.visibility = "public" 699 self.visibility = "public"
732 700
733 def parseBody(self, text): 701 def parseBody(self, text):
734 """ 702 """
735 Public method to parse the function body text. 703 Public method to parse the function body text.
736 704
737 @param text function body text 705 @param text function body text
738 @type str 706 @type str
739 """ 707 """
740 raiseRe = re.findall(r"[ \t]raise ([a-zA-Z0-9_]*)", text) 708 raiseRe = re.findall(r"[ \t]raise ([a-zA-Z0-9_]*)", text)
741 if len(raiseRe) > 0: 709 if len(raiseRe) > 0:
742 self.raiseList = [x.strip() for x in raiseRe] 710 self.raiseList = [x.strip() for x in raiseRe]
743 # remove duplicates from list while keeping it in the order 711 # remove duplicates from list while keeping it in the order
744 self.raiseList = list(collections.OrderedDict.fromkeys( 712 self.raiseList = list(collections.OrderedDict.fromkeys(self.raiseList))
745 self.raiseList))
746 713
747 yieldRe = re.search(r"[ \t]yield ", text) 714 yieldRe = re.search(r"[ \t]yield ", text)
748 if yieldRe: 715 if yieldRe:
749 self.hasYield = True 716 self.hasYield = True
750 717
755 returnTmpLine = "" 722 returnTmpLine = ""
756 723
757 for line in lineList: 724 for line in lineList:
758 line = line.strip() 725 line = line.strip()
759 726
760 if ( 727 if returnFound is False and re.match(returnPattern, line):
761 returnFound is False and
762 re.match(returnPattern, line)
763 ):
764 returnFound = True 728 returnFound = True
765 729
766 if returnFound: 730 if returnFound:
767 returnTmpLine += line 731 returnTmpLine += line
768 # check the integrity of line 732 # check the integrity of line
771 735
772 if returnTmpLine.endswith("\\"): 736 if returnTmpLine.endswith("\\"):
773 returnTmpLine = returnTmpLine[:-1] 737 returnTmpLine = returnTmpLine[:-1]
774 continue 738 continue
775 739
776 self.__findBracketPosition( 740 self.__findBracketPosition(returnTmpLine, "(", ")", quotePos)
777 returnTmpLine, "(", ")", quotePos) 741 self.__findBracketPosition(returnTmpLine, "{", "}", quotePos)
778 self.__findBracketPosition( 742 self.__findBracketPosition(returnTmpLine, "[", "]", quotePos)
779 returnTmpLine, "{", "}", quotePos)
780 self.__findBracketPosition(
781 returnTmpLine, "[", "]", quotePos)
782 except IndexError: 743 except IndexError:
783 continue 744 continue
784 745
785 returnValue = re.sub(returnPattern, "", returnTmpLine) 746 returnValue = re.sub(returnPattern, "", returnTmpLine)
786 self.returnValueInBody.append(returnValue) 747 self.returnValueInBody.append(returnValue)

eric ide

mercurial