11import ast
2- #from xml.sax.saxutils import escape
2+ import sys
3+ import traceback
4+
5+
6+ class CodeValidationError (Exception ):
7+ """Class raised when there is an exception within a WidgetCodeInput."""
8+
9+ def __init__ (self , msg , orig_exc ):
10+ super ().__init__ (msg )
11+ self .orig_exc = orig_exc
12+
313
414def is_valid_variable_name (name ):
515 """
6- Check if the value specified is a valid variable name (e.g. to
16+ Check if the value specified is a valid variable name (e.g. to
717 validate the function name)
818
919 :return: True if the name is valid, False otherwise
1020 """
1121 try :
12- ast .parse (' {} = None' .format (name ))
22+ ast .parse (" {} = None" .format (name ))
1323 return True
1424 except (SyntaxError , ValueError , TypeError ):
1525 return False
1626
27+
1728def prepend_indent (string , indent_level ):
1829 """
1930 Add a given indent before every line of the string
2031
2132 :string: string to which we want to append the indent
2233 :indent_level: integer number of spaces to prepend
2334 """
24- indented = "" .join ("{}{}\n " .format (" " * indent_level , line ) for line in string .splitlines ())
35+ indented = "" .join (
36+ "{}{}\n " .format (" " * indent_level , line ) for line in string .splitlines ()
37+ )
2538 return indented
26-
39+
40+
2741def build_pre_body (signature , docstring , indent_level = 4 ):
2842 """
2943 Prepare the part of the function before the function body
@@ -35,12 +49,13 @@ def build_pre_body(signature, docstring, indent_level=4):
3549 """
3650 if '"""' in docstring :
3751 raise ValueError ('Triple double quotes (""") not allowed in docstring' )
38-
52+
3953 return "{}\n {}" .format (
40- signature ,
54+ signature ,
4155 prepend_indent ('"""{}"""' .format (docstring ), indent_level = indent_level ),
4256 )
43-
57+
58+
4459def build_function (signature , docstring , body , indent_level = 4 ):
4560 """
4661 Return the full function content
@@ -54,9 +69,10 @@ def build_function(signature, docstring, body, indent_level=4):
5469 # adds already a \n at the end of each line
5570 return "{}{}" .format (
5671 build_pre_body (signature , docstring , indent_level = indent_level ),
57- prepend_indent (body , indent_level = indent_level )
72+ prepend_indent (body , indent_level = indent_level ),
5873 )
5974
75+
6076def build_signature (function_name , function_parameters ):
6177 """
6278 Given the function name and function parameters, returns the signature line
@@ -67,38 +83,45 @@ def build_signature(function_name, function_parameters):
6783 """
6884 return "def {}({}):" .format (function_name , function_parameters )
6985
70- # def build_function_signature(function_name, args, defaults=None, varargs=None, keywords=None):
71- # """
72- # For the following function:
73-
74- # def fn(a, b, c=1, d="a", *args):
75- # pass
76-
77- # we have:
78-
79- # - function_name = "fn"
80- # - args = ['a', 'b', 'c', 'd']
81- # - defaults = [1, 'a']
82- # - varargs = 'args'
83- # - keywords = None
84-
85- # For now, defaults are not implemented (they require e.g. to convert a string to its python representation with quotes)
86- # """
87- # assert is_valid_variable_name(function_name)
88- # for arg in args:
89- # assert is_valid_variable_name(function_name)
90- # if varargs is not None:
91- # assert is_valid_variable_name(varargs)
92- # if keywords is not None:
93- # assert is_valid_variable_name(keywords)
94-
95- # mangled_args = [arg for arg in args] # here one could put the logic for defaults as well
96- # if varargs is not None:
97- # mangled_args.append('*{}'.format(varargs))
98- # if keywords is not None:
99- # mangled_args.append('*{}'.format(keywords))
100-
101- # args_string = ", ".join(args)
102-
103- # signature="def {}({}):".format(function_name, args_string)
104- # return signature
86+
87+ def format_syntax_error_msg (exc ):
88+ """
89+ Return a string reproducing the output of a SyntaxError.
90+
91+ :param exc: The exception that is being processed.
92+ """
93+ se_args = exc .args [1 ]
94+ return f"""SyntaxError in code input: { exc .args [0 ]}
95+ File "{ se_args [0 ]} ", line { se_args [1 ]}
96+ { se_args [3 ]} { ' ' * max (0 , se_args [2 ] - 1 )} ^
97+ """
98+
99+
100+ def format_generic_error_msg (exc , code_widget ):
101+ """
102+ Return a string reproducing the traceback of a typical error.
103+ This includes line numbers, as well as neighboring lines.
104+
105+ It will require also the code_widget instance, to get the actual source code.
106+
107+ :note: this must be called from withou the exception, as it will get the current traceback state.
108+
109+ :param exc: The exception that is being processed.
110+ :param code_widget: the instance of the code widget with the code that raised the exception.
111+ """
112+ error_class , _ , tb = sys .exc_info ()
113+ line_number = traceback .extract_tb (tb )[- 1 ][1 ]
114+ code_lines = code_widget .full_function_code .splitlines ()
115+
116+ err_msg = f"{ error_class .__name__ } in code input: { str (exc )} \n "
117+ if line_number > 2 :
118+ err_msg += f" { line_number - 2 :4d} { code_lines [line_number - 3 ]} \n "
119+ if line_number > 1 :
120+ err_msg += f" { line_number - 1 :4d} { code_lines [line_number - 2 ]} \n "
121+ err_msg += f"---> { line_number :4d} { code_lines [line_number - 1 ]} \n "
122+ if line_number < len (code_lines ):
123+ err_msg += f" { line_number + 1 :4d} { code_lines [line_number ]} \n "
124+ if line_number < len (code_lines ) - 1 :
125+ err_msg += f" { line_number + 2 :4d} { code_lines [line_number + 1 ]} \n "
126+
127+ return err_msg
0 commit comments