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