47
47
"""
48
48
49
49
import argparse
50
+ import datetime as dt
51
+ import json
50
52
import os
51
53
import re
52
54
import sys
53
55
import xml .etree .ElementTree as ET # nosec
54
56
57
+ import requests
58
+
55
59
56
60
def remove_prefix (string , prefix ):
57
61
"""
@@ -64,27 +68,122 @@ def remove_prefix(string, prefix):
64
68
return string
65
69
66
70
67
- def convert_to_checkstyle ( messages , root_path = None ):
71
+ def convert_annotations_to_checkstyle ( annotations , root_path = None ):
68
72
"""
69
- Convert provided message to CheckStyle format.
73
+ Convert annotation list to CheckStyle xml string
70
74
"""
71
75
root = ET .Element ("checkstyle" )
72
- for message in messages :
73
- fields = parse_message (message )
74
- if fields :
75
- add_error_entry (root , ** fields , root_path = root_path )
76
+ for fields in annotations :
77
+ add_error_entry (root , ** fields , root_path = root_path )
76
78
return ET .tostring (root , encoding = "utf_8" ).decode ("utf_8" )
77
79
78
80
79
- def convert_text_to_checkstyle ( text , root_path = None ):
81
+ def convert_lines_to_annotations ( lines ):
80
82
"""
81
83
Convert provided message to CheckStyle format.
82
84
"""
83
- root = ET .Element ("checkstyle" )
84
- for fields in parse_file (text ):
85
+ annotations = []
86
+ for line in lines :
87
+ fields = parse_message (line )
85
88
if fields :
86
- add_error_entry (root , ** fields , root_path = root_path )
87
- return ET .tostring (root , encoding = "utf_8" ).decode ("utf_8" )
89
+ annotations .append (fields )
90
+ return annotations
91
+
92
+
93
+ def convert_text_to_annotations (text ):
94
+ """
95
+ Convert provided message to CheckStyle format.
96
+ """
97
+ return parse_file (text )
98
+
99
+
100
+ # Initial version for Checkrun from:
101
+ # https://github.com/tayfun/flake8-your-pr/blob/50a175cde4dd26a656734c5b64ba1e5bb27151cb/src/main.py#L7C1-L123C36
102
+ # MIT Licence
103
+ class CheckRun :
104
+ """
105
+ Represents the check run
106
+ """
107
+
108
+ GITHUB_TOKEN = os .environ .get ("GITHUB_TOKEN" , None )
109
+ GITHUB_EVENT_PATH = os .environ .get ("GITHUB_EVENT_PATH" , None )
110
+
111
+ URI = "https://api.github.com"
112
+ API_VERSION = "2022-11-28"
113
+ ACCEPT_HEADER_VALUE = f"application/vnd.github.{ API_VERSION } +json"
114
+ AUTH_HEADER_VALUE = f"token { GITHUB_TOKEN } "
115
+ # This is the max annotations Github API accepts in one go.
116
+ MAX_ANNOTATIONS = 50
117
+
118
+ def __init__ (self ):
119
+ """
120
+ Initialise Check Run object with information from checkrun
121
+ """
122
+ self .read_event_file ()
123
+ self .read_meta_data ()
124
+
125
+ def read_event_file (self ):
126
+ """
127
+ Read the event file to get the event information later.
128
+ """
129
+ if self .GITHUB_EVENT_PATH is None :
130
+ raise ValueError ("Not running in github workflow" )
131
+ with open (self .GITHUB_EVENT_PATH , encoding = "utf_8" ) as event_file :
132
+ self .event = json .loads (event_file .read ())
133
+
134
+ def read_meta_data (self ):
135
+ """
136
+ Get meta data from event information
137
+ """
138
+ self .repo_full_name = self .event ["repository" ]["full_name" ]
139
+ pull_request = self .event .get ("pull_request" )
140
+ if pull_request :
141
+ self .head_sha = pull_request ["head" ]["sha" ]
142
+ else :
143
+ check_suite = self .event ["check_suite" ]
144
+ self .head_sha = check_suite ["pull_requests" ][0 ]["base" ]["sha" ]
145
+
146
+ def submit ( # pylint: disable=too-many-arguments
147
+ self ,
148
+ annotations ,
149
+ title = None ,
150
+ summary = None ,
151
+ text = None ,
152
+ conclusion = None ,
153
+ ):
154
+ """
155
+ Submit annotations to github
156
+
157
+ :param conclusion: success, failure
158
+ """
159
+ output = {
160
+ "annotations" : annotations [: CheckRun .MAX_ANNOTATIONS ],
161
+ }
162
+ if title is not None :
163
+ output ["title" ] = title
164
+ output ["summary" ] = summary
165
+ output ["text" ] = text
166
+
167
+ payload = {
168
+ "name" : "log-to-pr-annotation" ,
169
+ "head_sha" : self .head_sha ,
170
+ "status" : "completed" ,
171
+ "conclusion" : conclusion ,
172
+ "completed_at" : dt .datetime .now (dt .timezone .utc ).isoformat (),
173
+ "output" : output ,
174
+ }
175
+
176
+ response = requests .post (
177
+ f"{ self .URI } /repos/{ self .repo_full_name } /check-runs" ,
178
+ headers = {
179
+ "Accept" : self .ACCEPT_HEADER_VALUE ,
180
+ "Authorization" : self .AUTH_HEADER_VALUE ,
181
+ },
182
+ json = payload ,
183
+ timeout = 30 ,
184
+ )
185
+ print (response .content )
186
+ response .raise_for_status ()
88
187
89
188
90
189
ANY_REGEX = r".*?"
@@ -405,6 +504,12 @@ def main():
405
504
" Defaults to working directory." ,
406
505
default = os .getcwd (),
407
506
)
507
+ parser .add_argument (
508
+ "--github-annotate" ,
509
+ action = argparse .BooleanOptionalAction ,
510
+ help = "Annotate when in Github workflow." ,
511
+ default = (os .environ .get ("GITHUB_EVENT_PATH" , None ) is not None ),
512
+ )
408
513
409
514
args = parser .parse_args ()
410
515
@@ -424,11 +529,13 @@ def main():
424
529
root_path = os .path .join (args .root , "" )
425
530
426
531
try :
427
- checkstyle_xml = convert_text_to_checkstyle (text , root_path = root_path )
532
+ annotations = convert_text_to_annotations (text )
428
533
except ImportError :
429
- checkstyle_xml = convert_to_checkstyle (
430
- re .split (r"[\r\n]+" , text ), root_path = root_path
431
- )
534
+ annotations = convert_lines_to_annotations (re .split (r"[\r\n]+" , text ))
535
+
536
+ checkstyle_xml = convert_annotations_to_checkstyle (
537
+ annotations , root_path = root_path
538
+ )
432
539
433
540
if args .output == "-" and args .output_named :
434
541
with open (args .output_named , "w" , encoding = "utf_8" ) as output_file :
@@ -439,6 +546,10 @@ def main():
439
546
else :
440
547
print (checkstyle_xml )
441
548
549
+ if args .github_annotate :
550
+ checkrun = CheckRun ()
551
+ checkrun .submit (annotations )
552
+
442
553
443
554
if __name__ == "__main__" :
444
555
main ()
0 commit comments