e768098d0e
Flake8 Lint / flake8 (push) Waiting to run
Spell check CI / Spell_Check (push) Waiting to run
tools_continuous_delivery / Private PyPI non-main branch release (push) Has been skipped
tools_continuous_delivery / Private PyPI main branch release (push) Failing after 2m42s
Publish Promptflow Doc / Build (push) Has been cancelled
Publish Promptflow Doc / Deploy (push) Has been cancelled
113 lines
4.9 KiB
Python
113 lines
4.9 KiB
Python
import logging
|
|
import re
|
|
from typing import List
|
|
|
|
|
|
class Settings:
|
|
divide_file = {
|
|
"py": r"(?<!.)(class|def)",
|
|
}
|
|
divide_func = {
|
|
"py": r"((\n {,6})|^)(class|def)\s+(\S+(?=\())\s*(\([^)]*\))?\s*(->[^:]*:|:) *"
|
|
}
|
|
|
|
|
|
class Divider:
|
|
language = 'py'
|
|
|
|
@classmethod
|
|
def divide_file(cls, text) -> List[str]:
|
|
matches = list(re.finditer(Settings.divide_file[Divider.language], text))
|
|
splitted_content = []
|
|
min_pos = matches[0].start() if len(matches) > 0 else len(text)
|
|
for i in range(len(matches)):
|
|
start = matches[i].start()
|
|
end = matches[i + 1].start() if i + 1 < len(matches) else len(text)
|
|
splitted_content.append(text[start:end])
|
|
if min_pos != 0:
|
|
splitted_content.insert(0, text[0:min_pos])
|
|
return splitted_content
|
|
|
|
@classmethod
|
|
def divide_half(cls, text) -> List[str]:
|
|
"""
|
|
Divide the content into two parts, but ensure that the function body is not split.
|
|
"""
|
|
_, pos = Divider.get_functions_and_pos(text)
|
|
if len(pos) > 1: # Divide the code into two parts and every part start with a function.
|
|
i = len(pos) // 2
|
|
return [text[0:pos[i][0]], text[pos[i][0]:]]
|
|
if len(pos) == 1: # Divide the code into two parts, [function define + body, other body].
|
|
body = text[pos[0][1]:]
|
|
body_lines = body.split('\n')
|
|
body_ten_lines = '\n'.join(body_lines[0:10])
|
|
return [text[0:pos[0][1]] + body_ten_lines, body[len(body_ten_lines):]]
|
|
return [text]
|
|
|
|
@classmethod
|
|
def get_functions_and_pos(cls, text):
|
|
matches = re.finditer(Settings.divide_func[Divider.language], text)
|
|
functions = []
|
|
pos = []
|
|
for match in matches:
|
|
matched_text = match.group().replace('\n', '')
|
|
func = re.sub(r' +', ' ', matched_text).replace(' :', ':')
|
|
func = re.sub(r'[\s,]+\)', ')', func)
|
|
func = re.sub(r'\([\s,]+', '(', func)
|
|
functions.append(func.strip())
|
|
pos.append((match.start(), match.end()))
|
|
return functions, pos
|
|
|
|
@classmethod
|
|
def combine(cls, divided: List[str]):
|
|
return ''.join(divided)
|
|
|
|
@classmethod
|
|
def merge_doc2code(cls, docstring: str, origin_code: str) -> str:
|
|
funcs1, pos1 = Divider.get_functions_and_pos(docstring)
|
|
funcs2, pos2 = Divider.get_functions_and_pos(origin_code)
|
|
pattern = r'""".*?"""'
|
|
code = origin_code if len(funcs2) == 0 else origin_code[0:pos2[0][0]]
|
|
pos1.append((len(docstring), len(docstring))) # avoid index out of range
|
|
pos2.append((len(origin_code), len(origin_code))) # avoid index out of range
|
|
for i2 in range(len(funcs2)): # add docstring for each function in origin_code
|
|
part_full_code = origin_code[pos2[i2][0]:pos2[i2 + 1][0]]
|
|
try:
|
|
i1 = funcs1.index(funcs2[i2])
|
|
except ValueError:
|
|
logging.warning(f"No docstring found for {funcs2[i2]}")
|
|
code += part_full_code
|
|
continue
|
|
new_doc = re.findall(pattern, docstring[pos1[i1][1]:pos1[i1 + 1][0]], re.DOTALL)
|
|
if new_doc:
|
|
func_line = origin_code[pos2[i2][0]:pos2[i2][1]].replace('\n', '')
|
|
empty_line_num = (len(func_line) - len(func_line.lstrip()) + 4)
|
|
func_body = origin_code[pos2[i2][1]:pos2[i2 + 1][0]]
|
|
code_doc = list(re.finditer(pattern, func_body, re.DOTALL))
|
|
format_new_doc = Divider.format_indentation(new_doc[0], empty_line_num)
|
|
is_replace_doc = len(code_doc) > 0 and (re.sub(r'\s+', '', func_body[0:code_doc[0].start()]) == '')
|
|
if is_replace_doc:
|
|
code += part_full_code.replace(code_doc[0].group(), format_new_doc.strip(), 1)
|
|
else:
|
|
code += origin_code[pos2[i2][0]:pos2[i2][1]] + '\n' + format_new_doc + '\n' + origin_code[
|
|
pos2[i2][1]:
|
|
pos2[i2 + 1][0]]
|
|
else:
|
|
code += part_full_code
|
|
return code
|
|
|
|
@classmethod
|
|
def format_indentation(cls, text, empty_line_num):
|
|
lines = text.splitlines()
|
|
last_line_space_num = len(lines[-1]) - len(lines[-1].lstrip())
|
|
need_add_space = max(empty_line_num - last_line_space_num, 0) * ' '
|
|
lines[0] = last_line_space_num * ' ' + lines[0].lstrip() # Align the first row to the last row
|
|
indented_lines = [(need_add_space + line).rstrip() for line in lines]
|
|
indented_string = '\n'.join(indented_lines)
|
|
return indented_string
|
|
|
|
@classmethod
|
|
def has_class_or_func(cls, text):
|
|
funcs, _ = Divider.get_functions_and_pos(text)
|
|
return len(funcs) > 0
|