1+ #!/usr/bin/env python3
2+ """Keep the "pending review" section of the master issue in sync.
3+
4+ Lists every open issue carrying the sync label (created by the
5+ "Sync with CPython" workflow) and rewrites that section of the master issue.
6+ If an issue has an open linked PR, "درخواست ادغام: #<PR>" is added next to it.
7+
8+ Only the text between the two HTML-comment markers is ever touched. On the
9+ first run the markers are inserted right below the section heading.
10+
11+ Env: GH_TOKEN, GITHUB_REPOSITORY (set by Actions); optional MASTER_ISSUE,
12+ REVIEW_LABEL.
13+ """
14+ import json
15+ import os
16+ import re
17+ import subprocess
18+ import sys
19+ import tempfile
20+
21+ REPO = os .environ ["GITHUB_REPOSITORY" ]
22+ MASTER = os .environ .get ("MASTER_ISSUE" , "83" )
23+ LABEL = os .environ .get ("REVIEW_LABEL" , "تغییرات خودکار در ترجمهها" )
24+ HEADING = "### تغییرات خودکارِ در انتظار بررسی"
25+ START = "<!-- auto-review:start -->"
26+ END = "<!-- auto-review:end -->"
27+
28+ QUERY = """
29+ query($owner: String!, $name: String!, $label: String!) {
30+ repository(owner: $owner, name: $name) {
31+ issues(first: 100, states: OPEN, labels: [$label],
32+ orderBy: {field: CREATED_AT, direction: ASC}) {
33+ nodes {
34+ number
35+ closedByPullRequestsReferences(first: 5, includeClosedPrs: false) {
36+ nodes { number state }
37+ }
38+ }
39+ }
40+ }
41+ }
42+ """
43+
44+
45+ def gh (* args ):
46+ return subprocess .run (
47+ ["gh" , * args ], check = True , capture_output = True , text = True
48+ ).stdout
49+
50+
51+ def fetch_items ():
52+ owner , name = REPO .split ("/" , 1 )
53+ out = gh (
54+ "api" , "graphql" ,
55+ "-f" , f"query={ QUERY } " ,
56+ "-F" , f"owner={ owner } " , "-F" , f"name={ name } " , "-f" , f"label={ LABEL } " ,
57+ )
58+ nodes = json .loads (out )["data" ]["repository" ]["issues" ]["nodes" ]
59+ items = []
60+ for n in nodes :
61+ if str (n ["number" ]) == str (MASTER ):
62+ continue
63+ prs = [
64+ p ["number" ]
65+ for p in n ["closedByPullRequestsReferences" ]["nodes" ]
66+ if p ["state" ] == "OPEN"
67+ ]
68+ items .append ((n ["number" ], prs ))
69+ return items
70+
71+
72+ def render (items ):
73+ if not items :
74+ return "_موردی در انتظار بررسی نیست._"
75+ lines = []
76+ for number , prs in items :
77+ line = f"- #{ number } "
78+ if prs :
79+ line += " — درخواست ادغام: " + "، " .join (f"#{ p } " for p in prs )
80+ lines .append (line )
81+ return "\n " .join (lines )
82+
83+
84+ def splice (body , block ):
85+ body = body .replace ("\r \n " , "\n " )
86+ section = f"{ START } \n { block } \n { END } "
87+ if START in body and END in body :
88+ pattern = re .compile (re .escape (START ) + r".*?" + re .escape (END ), re .S )
89+ return pattern .sub (lambda _ : section , body , count = 1 )
90+
91+ lines = body .split ("\n " )
92+ for i , line in enumerate (lines ):
93+ if line .strip () == HEADING :
94+ lines [i + 1 :i + 1 ] = ["" , section ]
95+ return "\n " .join (lines )
96+ raise SystemExit (f"Heading not found in issue #{ MASTER } : { HEADING } " )
97+
98+
99+ def main ():
100+ body = gh ("issue" , "view" , str (MASTER ), "--repo" , REPO ,
101+ "--json" , "body" , "-q" , ".body" )
102+ old = body .replace ("\r \n " , "\n " ).rstrip ("\n " )
103+ new = splice (body , render (fetch_items ())).rstrip ("\n " )
104+
105+ if new == old :
106+ print ("Master issue already up to date." )
107+ return
108+ with tempfile .NamedTemporaryFile ("w" , suffix = ".md" , delete = False ,
109+ encoding = "utf-8" ) as f :
110+ f .write (new + "\n " )
111+ gh ("issue" , "edit" , str (MASTER ), "--repo" , REPO , "--body-file" , f .name )
112+ print (f"Updated issue #{ MASTER } ." )
113+
114+
115+ if __name__ == "__main__" :
116+ sys .exit (main ())
0 commit comments