11import contextlib
22import faulthandler
3- import linecache
43import locale
54import math
65import os .path
@@ -142,63 +141,13 @@ def print_warning(msg: str) -> None:
142141orig_unraisablehook : Callable [..., None ] | None = None
143142
144143
145- def traceback_location (traceback : str ) -> dict [str , str | int ] | None :
146- """Location of the last frame of a traceback, relative to the repository.
147-
148- Return a dict with file and line, plus col and endColumn when the
149- traceback underlines the failing expression (~~~^^^).
150- """
151- lines = traceback .splitlines ()
152- for index in reversed (range (len (lines ))):
153- if match := re .match (r' File "(.+)", line (\d+)' , lines [index ]):
154- break
155- else :
156- return None
157- filename , lineno = match .group (1 ), int (match .group (2 ))
158- # Map the stdlib directory (Lib/ in a source checkout or an installed
159- # lib/python3.X/) to Lib/ in the repository
160- stdlib_dir = os .path .dirname (os .__file__ ) + os .sep
161- if not filename .startswith (stdlib_dir ):
162- return None
163- relpath = filename .removeprefix (stdlib_dir ).replace (os .sep , "/" )
164- location : dict [str , str | int ] = {"file" : f"Lib/{ relpath } " , "line" : lineno }
165-
166- # The frame is followed by the dedented source line (" code") and,
167- # for a single-line expression, by a line of ~ and ^ markers.
168- markers = lines [index + 2 ] if index + 2 < len (lines ) else ""
169- source = linecache .getline (filename , lineno )
170- if markers .strip () and not markers .strip (" ~^" ) and source :
171- indent = len (source ) - len (source .lstrip ())
172- start = len (markers ) - len (markers .lstrip ())
173- location ["col" ] = indent + start - 4 + 1
174- location ["endColumn" ] = indent + len (markers .rstrip ()) - 4
175- return location
176-
177-
178- def traceback_exception (traceback : str ) -> str :
179- """The exception which ends a traceback: type, message and notes.
180-
181- Return traceback unchanged if it has no frame.
182- """
183- lines = traceback .splitlines ()
184- for index in reversed (range (len (lines ))):
185- if lines [index ].startswith (' File "' ):
186- break
187- else :
188- return traceback
189- # Skip the frame's indented source line and ~^ markers
190- index += 1
191- while index < len (lines ) and lines [index ].startswith (" " ):
192- index += 1
193- return "\n " .join (lines [index :])
194-
195-
196- def github_annotation (title : str , message : str ) -> str :
144+ def github_annotation (title : str , message : str ,
145+ filename : str | None = None ) -> str :
197146 """Format a GitHub Actions error annotation.
198147
199- message is a traceback or a failure description. Locate the annotation at
200- the last frame of the traceback, if any, and only keep the exception which
201- ends the traceback: the job log has the full traceback.
148+ message is a traceback or a failure description. Only keep the exception
149+ which ends the traceback: the job log has the full traceback. Locate the
150+ annotation at the last frame of the traceback in filename, if any .
202151 """
203152 def escape (text : str ) -> str :
204153 return (text .replace ("%" , "%25" ).replace ("\r " , "%0D" )
@@ -208,12 +157,21 @@ def escape_property(text: str) -> str:
208157 return escape (text ).replace (":" , "%3A" ).replace ("," , "%2C" )
209158
210159 message = decolor (message )
211- props : dict [str , str | int ] = {}
212- if location := traceback_location (message ):
213- props |= location
160+ props : dict [str , str ] = {}
161+ # Map the stdlib directory (Lib/ in a source checkout or an installed
162+ # lib/python3.X/) to Lib/ in the repository
163+ stdlib_dir = os .path .dirname (os .__file__ ) + os .sep
164+ if filename and filename .startswith (stdlib_dir ):
165+ lines = re .findall (rf'^ File "{ re .escape (filename )} ", line (\d+)' ,
166+ message , re .MULTILINE )
167+ if lines :
168+ relpath = filename .removeprefix (stdlib_dir ).replace (os .sep , "/" )
169+ props |= {"file" : f"Lib/{ relpath } " , "line" : lines [- 1 ]}
214170 props ["title" ] = title
215- message = traceback_exception (message ).strip ()
216- props_text = "," .join (f"{ key } ={ escape_property (str (value ))} "
171+ # Strip the frames: each " File" line and its indented source lines
172+ message = re .split (r'^ File .*\n(?: .*\n)*' , message ,
173+ flags = re .MULTILINE )[- 1 ].strip ()
174+ props_text = "," .join (f"{ key } ={ escape_property (value )} "
217175 for key , value in props .items ())
218176 return f"::error { props_text } ::{ escape (message )} "
219177
0 commit comments