Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Bliss
bliss
Commits
b96519f0
Commit
b96519f0
authored
May 18, 2022
by
Lucas Felix
Browse files
Use a pygments lexer to format errors in repl
parent
30395cf2
Changes
2
Hide whitespace changes
Inline
Side-by-side
bliss/shell/cli/formatted_traceback.py
0 → 100644
View file @
b96519f0
import
os
import
sys
import
importlib
import
traceback
from
datetime
import
datetime
from
textwrap
import
indent
from
prompt_toolkit.formatted_text
import
PygmentsTokens
from
prompt_toolkit
import
print_formatted_text
from
pygments
import
lex
from
pygments.lexer
import
RegexLexer
,
bygroups
,
default
from
pygments.token
import
Token
class
BlissTracebackLexer
(
RegexLexer
):
"""Inspired by pygments.PythonTracebackLexer to colorize tracebacks, but for BlissTraceback
as well (it handles locals and timestamp)"""
name
=
"BlissTraceback"
tokens
=
{
"root"
:
[
(
r
"\n"
,
Token
.
Text
),
(
r
"\*[^\n]+\n"
,
Token
.
Comment
.
Preproc
),
(
r
"^[0-9]+\/[0-9]+\/[0-9]+ [0-9]+:[0-9]+:[0-9]+\n"
,
Token
.
Name
.
Property
),
(
r
"^Traceback \(most recent call last\):\n"
,
Token
.
Text
,
"intb"
),
(
r
"^During handling of the above exception, another exception occurred:\n\n"
,
Token
.
Heading
,
),
(
r
"^The above exception was the direct cause of the following exception:\n\n"
,
Token
.
Heading
,
),
(
r
'^(?= File "[^"]+", line \d+)'
,
Token
.
Generic
.
Traceback
,
"intb"
),
(
r
"^.*\n"
,
Token
.
Other
),
],
"intb"
:
[
(
r
'^( File )("[^"]+")(, line )(\d+)(, in )(.+)(\n)'
,
bygroups
(
Token
.
Text
,
Token
.
Name
.
Builtin
,
Token
.
Text
,
Token
.
Number
,
Token
.
Text
,
Token
.
Name
,
Token
.
Text
,
),
),
(
r
'^( File )("[^"]+")(, line )(\d+)(\n)'
,
bygroups
(
Token
.
Text
,
Token
.
Name
.
Builtin
,
Token
.
Text
,
Token
.
Number
,
Token
.
Text
),
),
(
r
"^(?= @.+\n)"
,
Token
.
Generic
.
Traceback
,
"markers"
),
(
r
"^( )(.+)(\n)"
,
bygroups
(
Token
.
Text
,
Token
.
Other
,
Token
.
Text
),
"markers"
,
),
(
r
"^([^:]+)(: )(.+)(\n)"
,
bygroups
(
Token
.
Generic
.
Error
,
Token
.
Text
,
Token
.
Name
.
Exception
,
Token
.
Text
),
"#pop"
,
),
(
r
"^([a-zA-Z_][\w.]*)(:?\n)"
,
bygroups
(
Token
.
Generic
.
Error
,
Token
.
Text
),
"#pop"
,
),
],
"markers"
:
[
(
r
"^( )\.\.\. \(truncated\)\n"
,
Token
.
Comment
.
Preproc
),
(
r
"^( )(@[^:]+)(: )(\.\.\. \(truncated\)\n)"
,
bygroups
(
Token
.
Text
,
Token
.
Name
.
Variable
,
Token
.
Punctuation
,
Token
.
Comment
.
Preproc
,
),
),
(
r
"^( )(@[^:]+)(:)(.+)(\n)"
,
bygroups
(
Token
.
Text
,
Token
.
Name
.
Variable
,
Token
.
Punctuation
,
Token
.
Literal
.
String
.
Other
,
Token
.
Text
,
),
),
(
r
"^[\w]*\n"
,
Token
.
Text
,
"#pop"
),
default
(
"#pop"
),
],
}
def
pprint_traceback
(
formatted_traceback
,
style
):
"""Print a formatted traceback (generic Python traceback or BlissTraceback) with colors,
using BlissTracebackLexer.
"""
tokens
=
list
(
lex
(
formatted_traceback
,
lexer
=
BlissTracebackLexer
()))
print_formatted_text
(
PygmentsTokens
(
tokens
),
end
=
""
,
style
=
style
)
class
BlissTraceback
:
"""Extract traceback content for later formatting without keeping any reference on
objects to avoid memory leaks. Then the format method can be used to produce various
formatting of the same traceback.
"""
_blacklist
=
[
# gevent & aiogevent module paths
os
.
path
.
dirname
(
importlib
.
util
.
find_spec
(
"gevent"
).
origin
),
os
.
path
.
dirname
(
importlib
.
util
.
find_spec
(
"aiogevent"
).
origin
),
# gevent compiled functions root path
"src/gevent"
,
]
def
__init__
(
self
,
exc_type
,
exc_value
,
tb
):
self
.
_datetime
=
datetime
.
now
()
# convert traceback to StackSummary to stringify references and avoid memory leak
self
.
_locals_capture_exc
=
""
try
:
traceback_exc
=
traceback
.
TracebackException
(
exc_type
,
exc_value
,
tb
,
capture_locals
=
True
)
except
Exception
as
e
:
# Capture_locals option fails as soon as one local's __repr__ fails.
# This will be fixed in python 3.11 with the addition of format_locals option,
# see https://github.com/python/cpython/pull/29299
# For the moment we can only disable capture_locals
self
.
_locals_capture_exc
=
(
traceback
.
format_tb
(
sys
.
exc_info
()[
2
],
-
1
)[
0
]
+
f
"
{
e
}
"
)
traceback_exc
=
traceback
.
TracebackException
(
exc_type
,
exc_value
,
tb
,
capture_locals
=
False
)
self
.
_exc_info
=
[]
iter
=
traceback_exc
while
iter
is
not
None
:
exc_type
=
iter
.
exc_type
exc_value
=
iter
.
_str
stack
=
iter
.
stack
msg
=
""
if
iter
.
__cause__
is
not
None
:
msg
=
"
\n
The above exception was the direct cause of the following exception:
\n\n
"
iter
=
iter
.
__cause__
elif
iter
.
__context__
is
not
None
:
msg
=
"
\n
During handling of the above exception, another exception occurred:
\n\n
"
iter
=
iter
.
__context__
else
:
iter
=
None
self
.
_exc_info
.
insert
(
0
,
(
exc_type
,
exc_value
,
stack
,
msg
))
def
_is_file_blacklisted
(
self
,
filename
):
for
black_path
in
BlissTraceback
.
_blacklist
:
if
filename
.
startswith
(
black_path
):
return
True
return
False
def
_format_stack
(
self
,
exc_type
,
exc_value
,
stack
,
msg
,
disable_blacklist
,
max_nb_locals
,
max_local_len
,
):
text
=
""
for
frame
in
stack
:
if
not
disable_blacklist
and
self
.
_is_file_blacklisted
(
frame
.
filename
):
continue
# skip bottom calls from ptpython
if
frame
.
filename
==
"<stdin>"
:
text
=
""
text
+=
f
' File "
{
frame
.
filename
}
", line
{
frame
.
lineno
}
, in
{
frame
.
name
}
\n
'
if
frame
.
_line
:
text
+=
f
"
{
frame
.
_line
}
\n
"
if
frame
.
locals
is
not
None
:
for
i
,
(
key
,
val
)
in
enumerate
(
sorted
(
frame
.
locals
.
items
())):
if
not
(
max_nb_locals
<
0
)
and
i
+
1
>
max_nb_locals
:
text
+=
" ... (truncated)
\n
"
break
if
len
(
val
)
<=
max_local_len
or
max_local_len
<
0
:
text
+=
f
" @
{
key
}
:
{
val
}
\n
"
else
:
text
+=
f
" @
{
key
}
: ... (truncated)
\n
"
text
+=
f
"
{
exc_type
.
__name__
}
:
{
exc_value
}
\n
"
return
msg
+
"Traceback (most recent call last):
\n
"
+
text
def
format
(
self
,
disable_blacklist
=
False
,
max_nb_locals
=-
1
,
max_local_len
=-
1
):
timestamp
=
self
.
_datetime
.
strftime
(
"%d/%m/%Y %H:%M:%S"
)
text
=
timestamp
+
"
\n
"
if
self
.
_locals_capture_exc
:
msg
=
"Can't display local variables along stack trace, "
msg
+=
"error occured during recovery:
\n
"
msg
+=
self
.
_locals_capture_exc
+
"
\n\n
"
text
+=
indent
(
msg
,
"* "
)
# Stack traces formatting
for
exc
in
self
.
_exc_info
:
text
+=
self
.
_format_stack
(
*
exc
,
disable_blacklist
,
max_nb_locals
,
max_local_len
)
return
text
bliss/shell/cli/repl.py
View file @
b96519f0
...
...
@@ -9,8 +9,6 @@
import
asyncio
from
typing
import
Optional
from
prompt_toolkit
import
print_formatted_text
from
prompt_toolkit.formatted_text
import
FormattedText
from
prompt_toolkit.styles.pygments
import
style_from_pygments_cls
from
pygments.styles
import
get_style_by_name
from
pygments.util
import
ClassNotFound
...
...
@@ -26,10 +24,8 @@ import gevent
import
signal
import
logging
import
platform
import
importlib
from
collections
import
deque
from
datetime
import
datetime
from
textwrap
import
indent
import
ptpython.layout
from
prompt_toolkit.patch_stdout
import
patch_stdout
as
patch_stdout_context
...
...
@@ -49,6 +45,7 @@ from bliss.shell.cli.ptpython_statusbar_patch import NEWstatus_bar, TMUXstatus_b
from
bliss.shell.bliss_banners
import
print_rainbow_banner
from
bliss.shell.cli.protected_dict
import
ProtectedDict
from
bliss.shell.cli.no_thread_repl
import
NoThreadPythonRepl
from
bliss.shell.cli.formatted_traceback
import
BlissTraceback
,
pprint_traceback
from
bliss.shell
import
standard
from
bliss
import
set_bliss_shell_mode
...
...
@@ -86,118 +83,6 @@ session_mdl.set_current_session = functools.partial(
# =================== ERROR REPORTING ============================
class
PrettyTraceback
:
_blacklist
=
[
# gevent & aiogevent module paths
os
.
path
.
dirname
(
importlib
.
util
.
find_spec
(
"gevent"
).
origin
),
os
.
path
.
dirname
(
importlib
.
util
.
find_spec
(
"aiogevent"
).
origin
),
# gevent compiled functions root path
"src/gevent"
,
]
def
__init__
(
self
,
exc_type
,
exc_value
,
tb
):
self
.
_datetime
=
datetime
.
now
()
self
.
_locals_capture_exc
=
""
# convert traceback to StackSummary to stringify references and avoid memory leak
try
:
traceback_exc
=
traceback
.
TracebackException
(
exc_type
,
exc_value
,
tb
,
capture_locals
=
True
)
except
Exception
as
e
:
# Capture_locals option fails as soon as one local's __repr__ fails.
# This will be fixed in python 3.11 with the addition of format_locals option,
# see https://github.com/python/cpython/pull/29299
# For the moment we can only disable capture_locals
self
.
_locals_capture_exc
=
(
traceback
.
format_tb
(
sys
.
exc_info
()[
2
],
-
1
)[
0
]
+
f
"
{
e
}
"
)
traceback_exc
=
traceback
.
TracebackException
(
exc_type
,
exc_value
,
tb
,
capture_locals
=
False
)
self
.
_exc_info
=
[]
iter
=
traceback_exc
while
iter
is
not
None
:
self
.
_exc_info
.
insert
(
0
,
(
iter
.
exc_type
,
iter
.
_str
,
iter
.
stack
))
iter
=
iter
.
__cause__
def
_is_blacklisted
(
self
,
filename
):
for
black_path
in
PrettyTraceback
.
_blacklist
:
if
filename
.
startswith
(
black_path
):
return
True
return
False
def
__str__
(
self
):
return
""
.
join
([
t
[
-
1
]
for
t
in
self
.
_format
()])
def
print_formatted
(
self
,
disable_blacklist
=
False
):
print_formatted_text
(
self
.
_format
(
disable_blacklist
),
style
=
BlissRepl
().
_current_style
,
file
=
sys
.
stderr
,
)
def
_format_stack
(
self
,
exc_type
,
exc_value
,
stack
,
disable_blacklist
=
False
):
tokens
=
[]
for
frame
in
stack
:
if
not
disable_blacklist
and
self
.
_is_blacklisted
(
frame
.
filename
):
continue
# skip bottom calls from ptpython
if
frame
.
filename
==
"<stdin>"
:
tokens
.
clear
()
tokens
+=
[
(
"class:pygments.literal"
,
frame
.
filename
),
(
"class:pygments.punctuation"
,
":"
),
(
"class:pygments.literal.number"
,
str
(
frame
.
lineno
)
+
" "
),
(
"class:pygments.name.function"
,
frame
.
name
+
"
\n
"
),
]
if
frame
.
_line
:
tokens
+=
[
(
"class:pygments.generic.error"
,
" > "
),
(
"class:pygments.text"
,
frame
.
_line
+
"
\n
"
),
]
# locals are not printed when frame._line is empty, this happens with traces coming
# from the interpreter or compiled files and locals dict is full of obscure symbols.
if
frame
.
locals
is
not
None
:
for
key
,
val
in
frame
.
locals
.
items
():
tokens
+=
[
(
"class:pygments.name.attribute"
,
key
),
(
"class:pygments.punctuation"
,
": "
),
(
"class:pygments.text"
,
val
+
"
\n
"
),
]
tokens
.
append
((
"class:pygments.generic.strong"
,
exc_type
.
__name__
+
":
\n
"
))
tokens
.
append
((
"class:pygments.name.exception"
,
str
(
exc_value
)
+
"
\n
"
))
return
tokens
def
_format
(
self
,
disable_blacklist
=
False
):
timestamp
=
self
.
_datetime
.
strftime
(
"%d/%m/%Y %H:%M:%S"
)
tokens
=
[(
"class:pygments.generic.heading"
,
f
"---
{
timestamp
}
---
\n
"
)]
if
self
.
_locals_capture_exc
:
msg
=
"Can't display local variables along stack trace, "
msg
+=
"error occured during recovery:
\n
"
msg
+=
self
.
_locals_capture_exc
+
"
\n\n
"
tokens
.
append
((
"class:pygments.comment.preproc"
,
indent
(
msg
,
"* "
)))
# Stack trace formatting
for
exc
in
self
.
_exc_info
[:
-
1
]:
tokens
+=
self
.
_format_stack
(
*
exc
)
tokens
.
append
(
(
"class:pygments.generic.traceback"
,
"
\n
The above exception was the direct cause of the following exception:
\n\n
"
,
)
)
tokens
+=
self
.
_format_stack
(
*
self
.
_exc_info
[
-
1
])
return
FormattedText
(
tokens
)
class
ErrorReport
(
ErrorReportInterface
):
"""
Manage the behavior of the error reporting in the shell.
...
...
@@ -242,15 +127,26 @@ def install_excepthook():
# filter exceptions from aiogevent(?) with no traceback, no value
return
error_report
.
history
.
append
(
PrettyTraceback
(
exc_type
,
exc_value
,
tb
))
# BlissTraceback captures traceback information without holding any reference on its content
fmt_tb
=
BlissTraceback
(
exc_type
,
exc_value
,
tb
)
# python generic formatting for the exception logger
exc_text
=
""
.
join
(
traceback
.
format_exception
(
exc_type
,
exc_value
,
tb
))
exc_logger
.
error
(
exc_text
)
# store BlissTraceback for later formatting
error_report
.
history
.
append
(
fmt_tb
)
# publish full error to logger
exc_logger
.
error
(
fmt_tb
.
format
(
disable_blacklist
=
True
))
# Adapt the error message depending on the expert_mode
if
error_report
.
_expert_mode
:
error_report
.
history
[
-
1
].
print_formatted
(
disable_blacklist
=
True
)
fmt_tb
=
error_report
.
history
[
-
1
].
format
(
disable_blacklist
=
True
)
try
:
style
=
BlissRepl
().
_current_style
except
Exception
:
# BlissRepl singleton is not instantiated yet
# falling back to monochrome
print
(
fmt_tb
)
else
:
pprint_traceback
(
fmt_tb
,
style
)
elif
current_session
:
if
current_session
.
is_loading_config
:
print
(
f
"
{
exc_type
.
__name__
}
:
{
exc_value
}
"
,
file
=
sys
.
stderr
)
...
...
@@ -798,9 +694,12 @@ def cli(
hist
=
user_ns
[
"ERROR_REPORT"
].
history
try
:
idx
=
-
1
if
index
is
None
else
index
hist
[
idx
].
print_formatted
(
disable_blacklist
=
user_ns
[
"ERROR_REPORT"
].
expert_mode
fmt_tb
=
hist
[
idx
].
format
(
disable_blacklist
=
user_ns
[
"ERROR_REPORT"
].
expert_mode
,
max_nb_locals
=
15
,
max_local_len
=
200
,
)
pprint_traceback
(
fmt_tb
,
BlissRepl
().
_current_style
)
except
IndexError
:
if
index
is
None
:
print
(
"None"
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment