File: C:/Users/fred/anaconda3/Lib/site-packages/spyder/plugins/editor/extensions/docstring.py
# -*- coding: utf-8 -*-
#
# Copyright © Spyder Project Contributors
# Licensed under the terms of the MIT License
# (see spyder/__init__.py for details)
"""Generate Docstring."""
# Standard library imports
import re
from collections import OrderedDict
# Third party imports
from qtpy.QtGui import QTextCursor
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QMenu
# Local imports
from spyder.config.manager import CONF
from spyder.py3compat import to_text_string
def is_start_of_function(text):
"""Return True if text is the beginning of the function definition."""
if isinstance(text, str):
function_prefix = ['def', 'async def']
text = text.lstrip()
for prefix in function_prefix:
if text.startswith(prefix):
return True
return False
def get_indent(text):
"""Get indent of text.
https://stackoverflow.com/questions/2268532/grab-a-lines-whitespace-
indention-with-python
"""
indent = ''
ret = re.match(r'(\s*)', text)
if ret:
indent = ret.group(1)
return indent
def is_in_scope_forward(text):
"""Check if the next empty line could be part of the definition."""
text = text.replace(r"\"", "").replace(r"\'", "")
scopes = ["'''", '"""', "'", '"']
indices = [10**6] * 4 # Limits function def length to 10**6
for i in range(len(scopes)):
if scopes[i] in text:
indices[i] = text.index(scopes[i])
if min(indices) == 10**6:
return (text.count(")") != text.count("(") or
text.count("]") != text.count("[") or
text.count("}") != text.count("{"))
s = scopes[indices.index(min(indices))]
p = indices[indices.index(min(indices))]
ls = len(s)
if s in text[p + ls:]:
text = text[:p] + text[p + ls:][text[p + ls:].index(s) + ls:]
return is_in_scope_forward(text)
elif ls == 3:
text = text[:p]
return (text.count(")") != text.count("(") or
text.count("]") != text.count("[") or
text.count("}") != text.count("{"))
else:
return False
def is_tuple_brackets(text):
"""Check if the return type is a tuple."""
scopes = ["(", "[", "{"]
complements = [")", "]", "}"]
indices = [10**6] * 4 # Limits return type length to 10**6
for i in range(len(scopes)):
if scopes[i] in text:
indices[i] = text.index(scopes[i])
if min(indices) == 10**6:
return "," in text
s = complements[indices.index(min(indices))]
p = indices[indices.index(min(indices))]
if s in text[p + 1:]:
text = text[:p] + text[p + 1:][text[p + 1:].index(s) + 1:]
return is_tuple_brackets(text)
else:
return False
def is_tuple_strings(text):
"""Check if the return type is a string."""
text = text.replace(r"\"", "").replace(r"\'", "")
scopes = ["'''", '"""', "'", '"']
indices = [10**6] * 4 # Limits return type length to 10**6
for i in range(len(scopes)):
if scopes[i] in text:
indices[i] = text.index(scopes[i])
if min(indices) == 10**6:
return is_tuple_brackets(text)
s = scopes[indices.index(min(indices))]
p = indices[indices.index(min(indices))]
ls = len(s)
if s in text[p + ls:]:
text = text[:p] + text[p + ls:][text[p + ls:].index(s) + ls:]
return is_tuple_strings(text)
else:
return False
def is_in_scope_backward(text):
"""Check if the next empty line could be part of the definition."""
return is_in_scope_forward(
text.replace(r"\"", "").replace(r"\'", "")[::-1])
class DocstringWriterExtension(object):
"""Class for insert docstring template automatically."""
def __init__(self, code_editor):
"""Initialize and Add code_editor to the variable."""
self.code_editor = code_editor
self.quote3 = '"""'
self.quote3_other = "'''"
self.line_number_cursor = None
@staticmethod
def is_beginning_triple_quotes(text):
"""Return True if there are only triple quotes in text."""
docstring_triggers = ['"""', 'r"""', "'''", "r'''"]
if text.lstrip() in docstring_triggers:
return True
return False
def is_end_of_function_definition(self, text, line_number):
"""Return True if text is the end of the function definition."""
text_without_whitespace = "".join(text.split())
if (
text_without_whitespace.endswith("):") or
text_without_whitespace.endswith("]:") or
(text_without_whitespace.endswith(":") and
"->" in text_without_whitespace)
):
return True
elif text_without_whitespace.endswith(":") and line_number > 1:
complete_text = text_without_whitespace
document = self.code_editor.document()
cursor = QTextCursor(
document.findBlockByNumber(line_number - 2)) # previous line
for i in range(line_number - 2, -1, -1):
txt = "".join(str(cursor.block().text()).split())
if txt.endswith("\\") or is_in_scope_backward(complete_text):
if txt.endswith("\\"):
txt = txt[:-1]
complete_text = txt + complete_text
else:
break
if i != 0:
cursor.movePosition(QTextCursor.PreviousBlock)
if is_start_of_function(complete_text):
return (
complete_text.endswith("):") or
complete_text.endswith("]:") or
(complete_text.endswith(":") and
"->" in complete_text)
)
else:
return False
else:
return False
def get_function_definition_from_first_line(self):
"""Get func def when the cursor is located on the first def line."""
document = self.code_editor.document()
cursor = QTextCursor(
document.findBlockByNumber(self.line_number_cursor - 1))
func_text = ''
func_indent = ''
is_first_line = True
line_number = cursor.blockNumber() + 1
number_of_lines = self.code_editor.blockCount()
remain_lines = number_of_lines - line_number + 1
number_of_lines_of_function = 0
for __ in range(min(remain_lines, 20)):
cur_text = to_text_string(cursor.block().text()).rstrip()
if is_first_line:
if not is_start_of_function(cur_text):
return None
func_indent = get_indent(cur_text)
is_first_line = False
else:
cur_indent = get_indent(cur_text)
if cur_indent <= func_indent and cur_text.strip() != '':
return None
if is_start_of_function(cur_text):
return None
if (cur_text.strip() == '' and
not is_in_scope_forward(func_text)):
return None
if len(cur_text) > 0 and cur_text[-1] == '\\':
cur_text = cur_text[:-1]
func_text += cur_text
number_of_lines_of_function += 1
if self.is_end_of_function_definition(
cur_text, line_number + number_of_lines_of_function - 1):
return func_text, number_of_lines_of_function
cursor.movePosition(QTextCursor.NextBlock)
return None
def get_function_definition_from_below_last_line(self):
"""Get func def when the cursor is located below the last def line."""
cursor = self.code_editor.textCursor()
func_text = ''
is_first_line = True
line_number = cursor.blockNumber() + 1
number_of_lines_of_function = 0
for __ in range(min(line_number, 20)):
if cursor.block().blockNumber() == 0:
return None
cursor.movePosition(QTextCursor.PreviousBlock)
prev_text = to_text_string(cursor.block().text()).rstrip()
if is_first_line:
if not self.is_end_of_function_definition(
prev_text, line_number - 1):
return None
is_first_line = False
elif self.is_end_of_function_definition(
prev_text, line_number - number_of_lines_of_function - 1):
return None
if len(prev_text) > 0 and prev_text[-1] == '\\':
prev_text = prev_text[:-1]
func_text = prev_text + func_text
number_of_lines_of_function += 1
if is_start_of_function(prev_text):
return func_text, number_of_lines_of_function
return None
def get_function_body(self, func_indent):
"""Get the function body text."""
cursor = self.code_editor.textCursor()
line_number = cursor.blockNumber() + 1
number_of_lines = self.code_editor.blockCount()
body_list = []
for __ in range(number_of_lines - line_number + 1):
text = to_text_string(cursor.block().text())
text_indent = get_indent(text)
if text.strip() == '':
pass
elif len(text_indent) <= len(func_indent):
break
body_list.append(text)
cursor.movePosition(QTextCursor.NextBlock)
return '\n'.join(body_list)
def write_docstring(self):
"""Write docstring to editor."""
line_to_cursor = self.code_editor.get_text('sol', 'cursor')
if self.is_beginning_triple_quotes(line_to_cursor):
cursor = self.code_editor.textCursor()
prev_pos = cursor.position()
quote = line_to_cursor[-1]
docstring_type = CONF.get('editor', 'docstring_type')
docstring = self._generate_docstring(docstring_type, quote)
if docstring:
self.code_editor.insert_text(docstring)
cursor = self.code_editor.textCursor()
cursor.setPosition(prev_pos, QTextCursor.KeepAnchor)
cursor.movePosition(QTextCursor.NextBlock)
cursor.movePosition(QTextCursor.EndOfLine,
QTextCursor.KeepAnchor)
cursor.clearSelection()
self.code_editor.setTextCursor(cursor)
return True
return False
def write_docstring_at_first_line_of_function(self):
"""Write docstring to editor at mouse position."""
result = self.get_function_definition_from_first_line()
editor = self.code_editor
if result:
func_text, number_of_line_func = result
line_number_function = (self.line_number_cursor +
number_of_line_func - 1)
cursor = editor.textCursor()
line_number_cursor = cursor.blockNumber() + 1
offset = line_number_function - line_number_cursor
if offset > 0:
for __ in range(offset):
cursor.movePosition(QTextCursor.NextBlock)
else:
for __ in range(abs(offset)):
cursor.movePosition(QTextCursor.PreviousBlock)
cursor.movePosition(QTextCursor.EndOfLine, QTextCursor.MoveAnchor)
editor.setTextCursor(cursor)
indent = get_indent(func_text)
editor.insert_text('\n{}{}"""'.format(indent, editor.indent_chars))
self.write_docstring()
def write_docstring_for_shortcut(self):
"""Write docstring to editor by shortcut of code editor."""
# cursor placed below function definition
result = self.get_function_definition_from_below_last_line()
if result is not None:
__, number_of_lines_of_function = result
cursor = self.code_editor.textCursor()
for __ in range(number_of_lines_of_function):
cursor.movePosition(QTextCursor.PreviousBlock)
self.code_editor.setTextCursor(cursor)
cursor = self.code_editor.textCursor()
self.line_number_cursor = cursor.blockNumber() + 1
self.write_docstring_at_first_line_of_function()
def _generate_docstring(self, doc_type, quote):
"""Generate docstring."""
docstring = None
self.quote3 = quote * 3
if quote == '"':
self.quote3_other = "'''"
else:
self.quote3_other = '"""'
result = self.get_function_definition_from_below_last_line()
if result:
func_def, __ = result
func_info = FunctionInfo()
func_info.parse_def(func_def)
if func_info.has_info:
func_body = self.get_function_body(func_info.func_indent)
if func_body:
func_info.parse_body(func_body)
if doc_type == 'Numpydoc':
docstring = self._generate_numpy_doc(func_info)
elif doc_type == 'Googledoc':
docstring = self._generate_google_doc(func_info)
elif doc_type == "Sphinxdoc":
docstring = self._generate_sphinx_doc(func_info)
return docstring
def _generate_numpy_doc(self, func_info):
"""Generate a docstring of numpy type."""
numpy_doc = ''
arg_names = func_info.arg_name_list
arg_types = func_info.arg_type_list
arg_values = func_info.arg_value_list
if len(arg_names) > 0 and arg_names[0] in ('self', 'cls'):
del arg_names[0]
del arg_types[0]
del arg_values[0]
indent1 = func_info.func_indent + self.code_editor.indent_chars
indent2 = func_info.func_indent + self.code_editor.indent_chars * 2
numpy_doc += '\n{}\n'.format(indent1)
if len(arg_names) > 0:
numpy_doc += '\n{}Parameters'.format(indent1)
numpy_doc += '\n{}----------\n'.format(indent1)
arg_text = ''
for arg_name, arg_type, arg_value in zip(arg_names, arg_types,
arg_values):
arg_text += '{}{} : '.format(indent1, arg_name)
if arg_type:
arg_text += '{}'.format(arg_type)
else:
arg_text += 'TYPE'
if arg_value:
arg_text += ', optional'
arg_text += '\n{}DESCRIPTION.'.format(indent2)
if arg_value:
arg_value = arg_value.replace(self.quote3, self.quote3_other)
arg_text += ' The default is {}.'.format(arg_value)
arg_text += '\n'
numpy_doc += arg_text
if func_info.raise_list:
numpy_doc += '\n{}Raises'.format(indent1)
numpy_doc += '\n{}------'.format(indent1)
for raise_type in func_info.raise_list:
numpy_doc += '\n{}{}'.format(indent1, raise_type)
numpy_doc += '\n{}DESCRIPTION.'.format(indent2)
numpy_doc += '\n'
numpy_doc += '\n'
if func_info.has_yield:
header = '{0}Yields\n{0}------\n'.format(indent1)
else:
header = '{0}Returns\n{0}-------\n'.format(indent1)
return_type_annotated = func_info.return_type_annotated
if return_type_annotated:
return_section = '{}{}{}'.format(header, indent1,
return_type_annotated)
return_section += '\n{}DESCRIPTION.'.format(indent2)
else:
return_element_type = indent1 + '{return_type}\n' + indent2 + \
'DESCRIPTION.'
placeholder = return_element_type.format(return_type='TYPE')
return_element_name = indent1 + '{return_name} : ' + \
placeholder.lstrip()
try:
return_section = self._generate_docstring_return_section(
func_info.return_value_in_body, header,
return_element_name, return_element_type, placeholder,
indent1)
except (ValueError, IndexError):
return_section = '{}{}None.'.format(header, indent1)
numpy_doc += return_section
numpy_doc += '\n\n{}{}'.format(indent1, self.quote3)
return numpy_doc
def _generate_google_doc(self, func_info):
"""Generate a docstring of google type."""
google_doc = ''
arg_names = func_info.arg_name_list
arg_types = func_info.arg_type_list
arg_values = func_info.arg_value_list
if len(arg_names) > 0 and arg_names[0] in ('self', 'cls'):
del arg_names[0]
del arg_types[0]
del arg_values[0]
indent1 = func_info.func_indent + self.code_editor.indent_chars
indent2 = func_info.func_indent + self.code_editor.indent_chars * 2
google_doc += '\n{}\n'.format(indent1)
if len(arg_names) > 0:
google_doc += '\n{0}Args:\n'.format(indent1)
arg_text = ''
for arg_name, arg_type, arg_value in zip(arg_names, arg_types,
arg_values):
arg_text += '{}{} '.format(indent2, arg_name)
arg_text += '('
if arg_type:
arg_text += '{}'.format(arg_type)
else:
arg_text += 'TYPE'
if arg_value:
arg_text += ', optional'
arg_text += '):'
arg_text += ' DESCRIPTION.'
if arg_value:
arg_value = arg_value.replace(self.quote3, self.quote3_other)
arg_text += ' Defaults to {}.\n'.format(arg_value)
else:
arg_text += '\n'
google_doc += arg_text
if func_info.raise_list:
google_doc += '\n{0}Raises:'.format(indent1)
for raise_type in func_info.raise_list:
google_doc += '\n{}{}'.format(indent2, raise_type)
google_doc += ': DESCRIPTION.'
google_doc += '\n'
google_doc += '\n'
if func_info.has_yield:
header = '{}Yields:\n'.format(indent1)
else:
header = '{}Returns:\n'.format(indent1)
return_type_annotated = func_info.return_type_annotated
if return_type_annotated:
return_section = '{}{}{}: DESCRIPTION.'.format(
header, indent2, return_type_annotated)
else:
return_element_type = indent2 + '{return_type}: DESCRIPTION.'
placeholder = return_element_type.format(return_type='TYPE')
return_element_name = indent2 + '{return_name} ' + \
'(TYPE): DESCRIPTION.'
try:
return_section = self._generate_docstring_return_section(
func_info.return_value_in_body, header,
return_element_name, return_element_type, placeholder,
indent2)
except (ValueError, IndexError):
return_section = '{}{}None.'.format(header, indent2)
google_doc += return_section
google_doc += '\n\n{}{}'.format(indent1, self.quote3)
return google_doc
def _generate_sphinx_doc(self, func_info):
"""Generate a docstring of sphinx type."""
sphinx_doc = ''
arg_names = func_info.arg_name_list
arg_types = func_info.arg_type_list
arg_values = func_info.arg_value_list
if len(arg_names) > 0 and arg_names[0] in ('self', 'cls'):
del arg_names[0]
del arg_types[0]
del arg_values[0]
indent1 = func_info.func_indent + self.code_editor.indent_chars
sphinx_doc += '\n{}\n'.format(indent1)
arg_text = ''
for arg_name, arg_type, arg_value in zip(arg_names, arg_types,
arg_values):
arg_text += '{}:param {}: DESCRIPTION'.format(indent1, arg_name)
if arg_value:
arg_value = arg_value.replace(self.quote3, self.quote3_other)
arg_text += ', defaults to {}\n'.format(arg_value)
else:
arg_text += '\n'
arg_text += '{}:type {}: '.format(indent1, arg_name)
if arg_type:
arg_text += '{}'.format(arg_type)
else:
arg_text += 'TYPE'
if arg_value:
arg_text += ', optional'
arg_text += '\n'
sphinx_doc += arg_text
if func_info.raise_list:
for raise_type in func_info.raise_list:
sphinx_doc += '{}:raises {}: DESCRIPTION\n'.format(indent1,
raise_type)
if func_info.has_yield:
header = '{}:yield:'.format(indent1)
else:
header = '{}:return:'.format(indent1)
return_type_annotated = func_info.return_type_annotated
if return_type_annotated:
return_section = '{} DESCRIPTION\n'.format(header)
return_section += '{}:rtype: {}'.format(indent1,
return_type_annotated)
else:
return_section = '{} DESCRIPTION\n'.format(header)
return_section += '{}:rtype: TYPE'.format(indent1)
sphinx_doc += return_section
sphinx_doc += '\n\n{}{}'.format(indent1, self.quote3)
return sphinx_doc
@staticmethod
def find_top_level_bracket_locations(string_toparse):
"""Get the locations of top-level brackets in a string."""
bracket_stack = []
replace_args_list = []
bracket_type = None
literal_type = ''
brackets = {'(': ')', '[': ']', '{': '}'}
for idx, character in enumerate(string_toparse):
if (not bracket_stack and character in brackets.keys()
or character == bracket_type):
bracket_stack.append(idx)
bracket_type = character
elif bracket_type and character == brackets[bracket_type]:
begin_idx = bracket_stack.pop()
if not bracket_stack:
if not literal_type:
if bracket_type == '(':
literal_type = '(None)'
elif bracket_type == '[':
literal_type = '[list]'
elif bracket_type == '{':
if idx - begin_idx <= 1:
literal_type = '{dict}'
else:
literal_type = '{set}'
replace_args_list.append(
(string_toparse[begin_idx:idx + 1],
literal_type, 1))
bracket_type = None
literal_type = ''
elif len(bracket_stack) == 1:
if bracket_type == '(' and character == ',':
literal_type = '(tuple)'
elif bracket_type == '{' and character == ':':
literal_type = '{dict}'
elif bracket_type == '(' and character == ':':
literal_type = '[slice]'
if bracket_stack:
raise IndexError('Bracket mismatch')
for replace_args in replace_args_list:
string_toparse = string_toparse.replace(*replace_args)
return string_toparse
@staticmethod
def parse_return_elements(return_vals_group, return_element_name,
return_element_type, placeholder):
"""Return the appropriate text for a group of return elements."""
all_eq = (return_vals_group.count(return_vals_group[0])
== len(return_vals_group))
if all([{'[list]', '(tuple)', '{dict}', '{set}'}.issuperset(
return_vals_group)]) and all_eq:
return return_element_type.format(
return_type=return_vals_group[0][1:-1])
# Output placeholder if special Python chars present in name
py_chars = {' ', '+', '-', '*', '/', '%', '@', '<', '>', '&', '|', '^',
'~', '=', ',', ':', ';', '#', '(', '[', '{', '}', ']',
')', }
if any([any([py_char in return_val for py_char in py_chars])
for return_val in return_vals_group]):
return placeholder
# Output str type and no name if only string literals
if all(['"' in return_val or '\'' in return_val
for return_val in return_vals_group]):
return return_element_type.format(return_type='str')
# Output bool type and no name if only bool literals
if {'True', 'False'}.issuperset(return_vals_group):
return return_element_type.format(return_type='bool')
# Output numeric types and no name if only numeric literals
try:
[float(return_val) for return_val in return_vals_group]
num_not_int = 0
for return_val in return_vals_group:
try:
int(return_val)
except ValueError: # If not an integer (EAFP)
num_not_int = num_not_int + 1
if num_not_int == 0:
return return_element_type.format(return_type='int')
elif num_not_int == len(return_vals_group):
return return_element_type.format(return_type='float')
else:
return return_element_type.format(return_type='numeric')
except ValueError: # Not a numeric if float conversion didn't work
pass
# If names are not equal, don't contain "." or are a builtin
if ({'self', 'cls', 'None'}.isdisjoint(return_vals_group) and all_eq
and all(['.' not in return_val
for return_val in return_vals_group])):
return return_element_name.format(return_name=return_vals_group[0])
return placeholder
def _generate_docstring_return_section(self, return_vals, header,
return_element_name,
return_element_type,
placeholder, indent):
"""Generate the Returns section of a function/method docstring."""
# If all return values are None, return none
non_none_vals = [return_val for return_val in return_vals
if return_val and return_val != 'None']
if not non_none_vals:
return header + indent + 'None.'
# Get only values with matching brackets that can be cleaned up
non_none_vals = [return_val.strip(' ()\t\n').rstrip(',')
for return_val in non_none_vals]
non_none_vals = [re.sub('([\"\'])(?:(?=(\\\\?))\\2.)*?\\1',
'"string"', return_val)
for return_val in non_none_vals]
unambiguous_vals = []
for return_val in non_none_vals:
try:
cleaned_val = self.find_top_level_bracket_locations(return_val)
except IndexError:
continue
unambiguous_vals.append(cleaned_val)
if not unambiguous_vals:
return header + placeholder
# If remaining are a mix of tuples and not, return single placeholder
single_vals, tuple_vals = [], []
for return_val in unambiguous_vals:
(tuple_vals.append(return_val) if ',' in return_val
else single_vals.append(return_val))
if single_vals and tuple_vals:
return header + placeholder
# If return values are tuples of different length, return a placeholder
if tuple_vals:
num_elements = [return_val.count(',') + 1
for return_val in tuple_vals]
if num_elements.count(num_elements[0]) != len(num_elements):
return header + placeholder
num_elements = num_elements[0]
else:
num_elements = 1
# If all have the same len but some ambiguous return that placeholders
if len(unambiguous_vals) != len(non_none_vals):
return header + '\n'.join(
[placeholder for __ in range(num_elements)])
# Handle tuple (or single) values position by position
return_vals_grouped = zip(*[
[return_element.strip() for return_element in
return_val.split(',')]
for return_val in unambiguous_vals])
return_elements_out = []
for return_vals_group in return_vals_grouped:
return_elements_out.append(
self.parse_return_elements(return_vals_group,
return_element_name,
return_element_type,
placeholder))
return header + '\n'.join(return_elements_out)
class FunctionInfo(object):
"""Parse function definition text."""
def __init__(self):
"""."""
self.has_info = False
self.func_text = ''
self.args_text = ''
self.func_indent = ''
self.arg_name_list = []
self.arg_type_list = []
self.arg_value_list = []
self.return_type_annotated = None
self.return_value_in_body = []
self.raise_list = None
self.has_yield = False
@staticmethod
def is_char_in_pairs(pos_char, pairs):
"""Return True if the character is in pairs of brackets or quotes."""
for pos_left, pos_right in pairs.items():
if pos_left < pos_char < pos_right:
return True
return False
@staticmethod
def _find_quote_position(text):
"""Return the start and end position of pairs of quotes."""
pos = {}
is_found_left_quote = False
for idx, character in enumerate(text):
if is_found_left_quote is False:
if character == "'" or character == '"':
is_found_left_quote = True
quote = character
left_pos = idx
else:
if character == quote and text[idx - 1] != '\\':
pos[left_pos] = idx
is_found_left_quote = False
if is_found_left_quote:
raise IndexError("No matching close quote at: " + str(left_pos))
return pos
def _find_bracket_position(self, text, bracket_left, bracket_right,
pos_quote):
"""Return the start and end position of pairs of brackets.
https://stackoverflow.com/questions/29991917/
indices-of-matching-parentheses-in-python
"""
pos = {}
pstack = []
for idx, character in enumerate(text):
if character == bracket_left and \
not self.is_char_in_pairs(idx, pos_quote):
pstack.append(idx)
elif character == bracket_right and \
not self.is_char_in_pairs(idx, pos_quote):
if len(pstack) == 0:
raise IndexError(
"No matching closing parens at: " + str(idx))
pos[pstack.pop()] = idx
if len(pstack) > 0:
raise IndexError(
"No matching opening parens at: " + str(pstack.pop()))
return pos
def split_arg_to_name_type_value(self, args_list):
"""Split argument text to name, type, value."""
for arg in args_list:
arg_type = None
arg_value = None
has_type = False
has_value = False
pos_colon = arg.find(':')
pos_equal = arg.find('=')
if pos_equal > -1:
has_value = True
if pos_colon > -1:
if not has_value:
has_type = True
elif pos_equal > pos_colon: # exception for def foo(arg1=":")
has_type = True
if has_value and has_type:
arg_name = arg[0:pos_colon].strip()
arg_type = arg[pos_colon + 1:pos_equal].strip()
arg_value = arg[pos_equal + 1:].strip()
elif not has_value and has_type:
arg_name = arg[0:pos_colon].strip()
arg_type = arg[pos_colon + 1:].strip()
elif has_value and not has_type:
arg_name = arg[0:pos_equal].strip()
arg_value = arg[pos_equal + 1:].strip()
else:
arg_name = arg.strip()
self.arg_name_list.append(arg_name)
self.arg_type_list.append(arg_type)
self.arg_value_list.append(arg_value)
def split_args_text_to_list(self, args_text):
"""Split the text including multiple arguments to list.
This function uses a comma to separate arguments and ignores a comma in
brackets and quotes.
"""
args_list = []
idx_find_start = 0
idx_arg_start = 0
try:
pos_quote = self._find_quote_position(args_text)
pos_round = self._find_bracket_position(args_text, '(', ')',
pos_quote)
pos_curly = self._find_bracket_position(args_text, '{', '}',
pos_quote)
pos_square = self._find_bracket_position(args_text, '[', ']',
pos_quote)
except IndexError:
return None
while True:
pos_comma = args_text.find(',', idx_find_start)
if pos_comma == -1:
break
idx_find_start = pos_comma + 1
if self.is_char_in_pairs(pos_comma, pos_round) or \
self.is_char_in_pairs(pos_comma, pos_curly) or \
self.is_char_in_pairs(pos_comma, pos_square) or \
self.is_char_in_pairs(pos_comma, pos_quote):
continue
args_list.append(args_text[idx_arg_start:pos_comma])
idx_arg_start = pos_comma + 1
if idx_arg_start < len(args_text):
args_list.append(args_text[idx_arg_start:])
return args_list
def parse_def(self, text):
"""Parse the function definition text."""
self.__init__()
if not is_start_of_function(text):
return
self.func_indent = get_indent(text)
text = text.strip()
return_type_re = re.search(
r'->[ ]*([\"\'a-zA-Z0-9_,()\[\] ]*):$', text)
if return_type_re:
self.return_type_annotated = return_type_re.group(1).strip(" ()\\")
if is_tuple_strings(self.return_type_annotated):
self.return_type_annotated = (
"(" + self.return_type_annotated + ")"
)
text_end = text.rfind(return_type_re.group(0))
else:
self.return_type_annotated = None
text_end = len(text)
pos_args_start = text.find('(') + 1
pos_args_end = text.rfind(')', pos_args_start, text_end)
self.args_text = text[pos_args_start:pos_args_end]
args_list = self.split_args_text_to_list(self.args_text)
if args_list is not None:
self.has_info = True
self.split_arg_to_name_type_value(args_list)
def parse_body(self, text):
"""Parse the function body text."""
re_raise = re.findall(r'[ \t]raise ([a-zA-Z0-9_]*)', text)
if len(re_raise) > 0:
self.raise_list = [x.strip() for x in re_raise]
# remove duplicates from list while keeping it in the order
# in python 2.7
# stackoverflow.com/questions/7961363/removing-duplicates-in-lists
self.raise_list = list(OrderedDict.fromkeys(self.raise_list))
re_yield = re.search(r'[ \t]yield ', text)
if re_yield:
self.has_yield = True
# get return value
pattern_return = r'return |yield '
line_list = text.split('\n')
is_found_return = False
line_return_tmp = ''
for line in line_list:
line = line.strip()
if is_found_return is False:
if re.match(pattern_return, line):
is_found_return = True
if is_found_return:
line_return_tmp += line
# check the integrity of line
try:
pos_quote = self._find_quote_position(line_return_tmp)
if line_return_tmp[-1] == '\\':
line_return_tmp = line_return_tmp[:-1]
continue
self._find_bracket_position(line_return_tmp, '(', ')',
pos_quote)
self._find_bracket_position(line_return_tmp, '{', '}',
pos_quote)
self._find_bracket_position(line_return_tmp, '[', ']',
pos_quote)
except IndexError:
continue
return_value = re.sub(pattern_return, '', line_return_tmp)
self.return_value_in_body.append(return_value)
is_found_return = False
line_return_tmp = ''
class QMenuOnlyForEnter(QMenu):
"""The class executes the selected action when "enter key" is input.
If a input of keyboard is not the "enter key", the menu is closed and
the input is inserted to code editor.
"""
def __init__(self, code_editor):
"""Init QMenu."""
super(QMenuOnlyForEnter, self).__init__(code_editor)
self.code_editor = code_editor
def keyPressEvent(self, event):
"""Close the instance if key is not enter key."""
key = event.key()
if key not in (Qt.Key_Enter, Qt.Key_Return):
self.code_editor.keyPressEvent(event)
self.close()
else:
super(QMenuOnlyForEnter, self).keyPressEvent(event)