2222import shlex
2323import sys
2424import tempfile
25+ import textwrap
2526from typing import Dict , Optional , TextIO
2627
27-
2828logger = logging .getLogger ()
2929logger .setLevel (logging .DEBUG )
3030handler = logging .StreamHandler (sys .stderr )
@@ -61,6 +61,7 @@ def write_commit(io: TextIO, title: str, body: str):
6161 io .write (body .encode ())
6262 io .flush ()
6363
64+
6465def parse_trailers (title , body ) -> Dict :
6566 trailers = defaultdict (list )
6667
@@ -77,6 +78,29 @@ def parse_trailers(title, body) -> Dict:
7778 return trailers
7879
7980
81+ def split_paragraphs (text : str ):
82+ """
83+ Split the given text into a generator of paragraph lines and a boolean "markdown" flag.
84+
85+ If any line of a paragraph starts with a markdown character, we will assume the whole paragraph
86+ contains markdown.
87+ """
88+ lines = text .splitlines (keepends = True )
89+ paragraph = []
90+ markdown = False
91+ for line in lines :
92+ if line .strip () == "" :
93+ if len (paragraph ) > 0 :
94+ yield paragraph , markdown
95+ paragraph .clear ()
96+ markdown = False
97+ else :
98+ if line [0 ] in ("#" , "*" , "-" , "=" ) or line [0 ].isdigit ():
99+ markdown = True
100+ paragraph .append (line )
101+ yield paragraph , markdown
102+
103+
80104if __name__ == "__main__" :
81105 """
82106 This script performs some basic linting of our PR titles and body. The PR number is read from the PR_NUMBER
@@ -96,10 +120,6 @@ def parse_trailers(title, body) -> Dict:
96120 * Has "Reviewers:" trailer if the PR is approved
97121 """
98122
99- if not get_env ("GITHUB_ACTIONS" ):
100- print ("This script is intended to by run by GitHub Actions." )
101- exit (1 )
102-
103123 pr_number = get_env ("PR_NUMBER" )
104124 cmd = f"gh pr view { pr_number } --json 'title,body,reviews'"
105125 p = subprocess .run (shlex .split (cmd ), capture_output = True )
@@ -132,6 +152,32 @@ def check(positive_assertion, ok_msg, err_msg):
132152 check ("Delete this text and replace" not in body , "PR template text not present" , "PR template text should be removed" )
133153 check ("Committer Checklist" not in body , "PR template text not present" , "Old PR template text should be removed" )
134154
155+ paragraph_iter = split_paragraphs (body )
156+ new_paragraphs = []
157+ for p , markdown in paragraph_iter :
158+ if markdown :
159+ # If a paragraph looks like it has markdown in it, wrap each line separately.
160+ new_lines = []
161+ for line in p :
162+ new_lines .append (textwrap .fill (line , width = 72 , break_long_words = False , break_on_hyphens = False , replace_whitespace = False ))
163+ rewrapped_p = "\n " .join (new_lines )
164+ else :
165+ rewrapped_p = textwrap .fill ("" .join (p ), width = 72 , break_long_words = False , break_on_hyphens = False , replace_whitespace = True )
166+ new_paragraphs .append (rewrapped_p + "\n " )
167+ body = "\n " .join (new_paragraphs )
168+
169+ if get_env ("GITHUB_ACTIONS" ):
170+ with tempfile .NamedTemporaryFile () as fp :
171+ fp .write (body .encode ())
172+ fp .flush ()
173+ cmd = f"gh pr edit { pr_number } --body-file { fp .name } "
174+ p = subprocess .run (shlex .split (cmd ), capture_output = True )
175+ fp .close ()
176+ if p .returncode != 0 :
177+ logger .error (f"Could not update PR { pr_number } . STDOUT: { p .stdout .decode ()} " )
178+ else :
179+ logger .info (f"Not reformatting { pr_number } since this is not running on GitHub Actions." )
180+
135181 # Check for Reviewers
136182 approved = has_approval (reviews )
137183 if approved :
@@ -143,12 +189,13 @@ def check(positive_assertion, ok_msg, err_msg):
143189 logger .debug (reviewer_in_body )
144190
145191 logger .debug ("Commit will look like:\n " )
146- logger .debug ("``` " )
192+ logger .debug ("<pre> " )
147193 io = BytesIO ()
194+ title += f" (#{ pr_number } )"
148195 write_commit (io , title , body )
149196 io .seek (0 )
150197 logger .debug (io .read ().decode ())
151- logger .debug ("``` \n " )
198+ logger .debug ("</pre> \n " )
152199
153200 exit_code = 0
154201 logger .debug ("Validation results:" )
0 commit comments