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