1919# Number of the 'Libc++ Standards Conformance' project on Github
2020LIBCXX_CONFORMANCE_PROJECT = '31'
2121
22+ class PaperStatus :
23+ TODO = 1
24+ IN_PROGRESS = 2
25+ PARTIAL = 3
26+ DONE = 4
27+ NOTHING_TO_DO = 5
28+
29+ _status : int
30+
31+ _original : Optional [str ]
32+ """
33+ Optional string from which the paper status was created. This is used to carry additional
34+ information from CSV rows, like any notes associated to the status.
35+ """
36+
37+ def __init__ (self , status : int , original : Optional [str ] = None ):
38+ self ._status = status
39+ self ._original = original
40+
41+ def __eq__ (self , other ) -> bool :
42+ return self ._status == other ._status
43+
44+ def __lt__ (self , other ) -> bool :
45+ relative_order = {
46+ PaperStatus .TODO : 0 ,
47+ PaperStatus .IN_PROGRESS : 1 ,
48+ PaperStatus .PARTIAL : 2 ,
49+ PaperStatus .DONE : 3 ,
50+ PaperStatus .NOTHING_TO_DO : 3 ,
51+ }
52+ return relative_order [self ._status ] < relative_order [other ._status ]
53+
54+ @staticmethod
55+ def from_csv_entry (entry : str ):
56+ """
57+ Parse a paper status out of a CSV row entry. Entries can look like:
58+ - '' (an empty string, which means the paper is not done yet)
59+ - '|In Progress|'
60+ - '|Partial|'
61+ - '|Complete|'
62+ - '|Nothing To Do|'
63+
64+ Note that since we sometimes add additional notes after the status, we only check that the entry
65+ starts with the above patterns.
66+ """
67+ if entry == '' :
68+ return PaperStatus (PaperStatus .TODO , entry )
69+ elif entry .startswith ('|In Progress|' ):
70+ return PaperStatus (PaperStatus .IN_PROGRESS , entry )
71+ elif entry .startswith ('|Partial|' ):
72+ return PaperStatus (PaperStatus .PARTIAL , entry )
73+ elif entry .startswith ('|Complete|' ):
74+ return PaperStatus (PaperStatus .DONE , entry )
75+ elif entry .startswith ('|Nothing To Do|' ):
76+ return PaperStatus (PaperStatus .NOTHING_TO_DO , entry )
77+ else :
78+ raise RuntimeError (f'Unexpected CSV entry for status: { entry } ' )
79+
80+ @staticmethod
81+ def from_github_issue (issue : Dict ):
82+ """
83+ Parse a paper status out of a Github issue obtained from querying a Github project.
84+ """
85+ if 'status' not in issue :
86+ return PaperStatus (PaperStatus .TODO )
87+ elif issue ['status' ] == 'Todo' :
88+ return PaperStatus (PaperStatus .TODO )
89+ elif issue ['status' ] == 'In Progress' :
90+ return PaperStatus (PaperStatus .IN_PROGRESS )
91+ elif issue ['status' ] == 'Partial' :
92+ return PaperStatus (PaperStatus .PARTIAL )
93+ elif issue ['status' ] == 'Done' :
94+ return PaperStatus (PaperStatus .DONE )
95+ elif issue ['status' ] == 'Nothing To Do' :
96+ return PaperStatus (PaperStatus .NOTHING_TO_DO )
97+ else :
98+ raise RuntimeError (f"Received unrecognizable Github issue status: { issue ['status' ]} " )
99+
100+ def to_csv_entry (self ) -> str :
101+ """
102+ Return the issue state formatted for a CSV entry. The status is formatted as '|Complete|',
103+ '|In Progress|', etc.
104+ """
105+ mapping = {
106+ PaperStatus .TODO : '' ,
107+ PaperStatus .IN_PROGRESS : '|In Progress|' ,
108+ PaperStatus .PARTIAL : '|Partial|' ,
109+ PaperStatus .DONE : '|Complete|' ,
110+ PaperStatus .NOTHING_TO_DO : '|Nothing To Do|' ,
111+ }
112+ return self ._original if self ._original is not None else mapping [self ._status ]
113+
114+ def is_done (self ) -> bool :
115+ return self ._status == PaperStatus .DONE or self ._status == PaperStatus .NOTHING_TO_DO
116+
22117class PaperInfo :
23118 paper_number : str
24119 """
@@ -30,15 +125,14 @@ class PaperInfo:
30125 Plain text string representing the name of the paper.
31126 """
32127
33- meeting : Optional [ str ]
128+ status : PaperStatus
34129 """
35- Plain text string representing the meeting at which the paper/issue was voted .
130+ Status of the paper/issue. This can be complete, in progress, partial, or done .
36131 """
37132
38- status : Optional [str ]
133+ meeting : Optional [str ]
39134 """
40- Status of the paper/issue. This must be '|Complete|', '|Nothing To Do|', '|In Progress|',
41- '|Partial|' or 'Resolved by <something>'.
135+ Plain text string representing the meeting at which the paper/issue was voted.
42136 """
43137
44138 first_released_version : Optional [str ]
@@ -59,15 +153,15 @@ class PaperInfo:
59153 """
60154
61155 def __init__ (self , paper_number : str , paper_name : str ,
156+ status : PaperStatus ,
62157 meeting : Optional [str ] = None ,
63- status : Optional [str ] = None ,
64158 first_released_version : Optional [str ] = None ,
65159 labels : Optional [List [str ]] = None ,
66160 original : Optional [object ] = None ):
67161 self .paper_number = paper_number
68162 self .paper_name = paper_name
69- self .meeting = meeting
70163 self .status = status
164+ self .meeting = meeting
71165 self .first_released_version = first_released_version
72166 self .labels = labels
73167 self .original = original
@@ -77,21 +171,14 @@ def for_printing(self) -> Tuple[str, str, str, str, str, str]:
77171 f'`{ self .paper_number } <https://wg21.link/{ self .paper_number } >`__' ,
78172 self .paper_name ,
79173 self .meeting if self .meeting is not None else '' ,
80- self .status if self . status is not None else '' ,
174+ self .status . to_csv_entry () ,
81175 self .first_released_version if self .first_released_version is not None else '' ,
82176 ' ' .join (f'|{ label } |' for label in self .labels ) if self .labels is not None else '' ,
83177 )
84178
85179 def __repr__ (self ) -> str :
86180 return repr (self .original ) if self .original is not None else repr (self .for_printing ())
87181
88- def is_implemented (self ) -> bool :
89- if self .status is None :
90- return False
91- if re .search (r'(in progress|partial)' , self .status .lower ()):
92- return False
93- return True
94-
95182 @staticmethod
96183 def from_csv_row (row : Tuple [str , str , str , str , str , str ]):# -> PaperInfo:
97184 """
@@ -105,8 +192,8 @@ def from_csv_row(row: Tuple[str, str, str, str, str, str]):# -> PaperInfo:
105192 return PaperInfo (
106193 paper_number = match .group (1 ),
107194 paper_name = row [1 ],
195+ status = PaperStatus .from_csv_entry (row [3 ]),
108196 meeting = row [2 ] or None ,
109- status = row [3 ] or None ,
110197 first_released_version = row [4 ] or None ,
111198 labels = [l .strip ('|' ) for l in row [5 ].split (' ' ) if l ] or None ,
112199 original = row ,
@@ -123,21 +210,15 @@ def from_github_issue(issue: Dict):# -> PaperInfo:
123210 raise RuntimeError (f"Issue doesn't have a title that we know how to parse: { issue } " )
124211 paper = match .group (1 )
125212
126- # Figure out the status of the paper according to the Github project information.
127- #
128- # Sadly, we can't make a finer-grained distiction about *how* the issue
129- # was closed (such as Nothing To Do or similar).
130- status = '|Complete|' if 'status' in issue and issue ['status' ] == 'Done' else None
131-
132213 # Handle labels
133214 valid_labels = ('format' , 'ranges' , 'spaceship' , 'flat_containers' , 'concurrency TS' , 'DR' )
134215 labels = [label for label in issue ['labels' ] if label in valid_labels ]
135216
136217 return PaperInfo (
137218 paper_number = paper ,
138219 paper_name = issue ['title' ],
220+ status = PaperStatus .from_github_issue (issue ),
139221 meeting = issue .get ('meeting Voted' , None ),
140- status = status ,
141222 first_released_version = None , # TODO
142223 labels = labels if labels else None ,
143224 original = issue ,
@@ -177,30 +258,34 @@ def sync_csv(rows: List[Tuple], from_github: List[PaperInfo]) -> List[Tuple]:
177258
178259 paper = PaperInfo .from_csv_row (row )
179260
180- # If the row is already implemented, basically keep it unchanged but also validate that we're not
181- # out-of-sync with any still-open Github issue tracking the same paper.
182- if paper .is_implemented ():
183- dangling = [gh for gh in from_github if gh .paper_number == paper .paper_number and not gh .is_implemented ()]
184- if dangling :
185- print (f"We found the following open tracking issues for a row which is already marked as implemented:\n row: { row } \n tracking issues: { dangling } " )
186- print ("The Github issue should be closed if the work has indeed been done." )
187- results .append (paper .for_printing ())
188- else :
189- # Find any Github issues tracking this paper
190- tracking = [gh for gh in from_github if paper .paper_number == gh .paper_number ]
261+ # Find any Github issues tracking this paper. Each row must have one and exactly one Github
262+ # issue tracking it, which we validate below.
263+ tracking = [gh for gh in from_github if paper .paper_number == gh .paper_number ]
191264
192- # If there is no tracking issue for that row in the CSV, this is an error since we're
193- # missing a Github issue.
194- if not tracking :
195- raise RuntimeError (f"Can't find any Github issue for CSV row which isn't marked as done yet: { row } " )
265+ # If there is no tracking issue for that row in the CSV, this is an error since we're
266+ # missing a Github issue.
267+ if len (tracking ) == 0 :
268+ print (f"Can't find any Github issue for CSV row: { row } " )
269+ results .append (row )
270+ continue
196271
197- # If there's more than one tracking issue, something is weird too.
198- if len (tracking ) > 1 :
199- raise RuntimeError (f"Found a row with more than one tracking issue: { row } \n tracked by: { tracking } " )
272+ # If there's more than one tracking issue, something is weird too.
273+ if len (tracking ) > 1 :
274+ print (f"Found a row with more than one tracking issue: { row } \n tracked by: { tracking } " )
275+ results .append (row )
276+ continue
200277
201- # If the issue is closed, synchronize the row based on the Github issue. Otherwise, use the
202- # existing CSV row as-is.
203- results .append (tracking [0 ].for_printing () if tracking [0 ].is_implemented () else row )
278+ gh = tracking [0 ]
279+
280+ # If the CSV row has a status that is "less advanced" than the Github issue, simply update the CSV
281+ # row with the newer status. Otherwise, report an error if they have a different status because
282+ # something must be wrong.
283+ if paper .status < gh .status :
284+ results .append (gh .for_printing ())
285+ continue
286+ elif paper .status != gh .status :
287+ print (f"We found a CSV row and a Github issue with different statuses:\n row: { row } \Github issue: { gh } " )
288+ results .append (row )
204289
205290 return results
206291
0 commit comments