19
19
# Number of the 'Libc++ Standards Conformance' project on Github
20
20
LIBCXX_CONFORMANCE_PROJECT = '31'
21
21
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
+
22
117
class PaperInfo :
23
118
paper_number : str
24
119
"""
@@ -30,15 +125,14 @@ class PaperInfo:
30
125
Plain text string representing the name of the paper.
31
126
"""
32
127
33
- meeting : Optional [ str ]
128
+ status : PaperStatus
34
129
"""
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 .
36
131
"""
37
132
38
- status : Optional [str ]
133
+ meeting : Optional [str ]
39
134
"""
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.
42
136
"""
43
137
44
138
first_released_version : Optional [str ]
@@ -59,15 +153,15 @@ class PaperInfo:
59
153
"""
60
154
61
155
def __init__ (self , paper_number : str , paper_name : str ,
156
+ status : PaperStatus ,
62
157
meeting : Optional [str ] = None ,
63
- status : Optional [str ] = None ,
64
158
first_released_version : Optional [str ] = None ,
65
159
labels : Optional [List [str ]] = None ,
66
160
original : Optional [object ] = None ):
67
161
self .paper_number = paper_number
68
162
self .paper_name = paper_name
69
- self .meeting = meeting
70
163
self .status = status
164
+ self .meeting = meeting
71
165
self .first_released_version = first_released_version
72
166
self .labels = labels
73
167
self .original = original
@@ -77,21 +171,14 @@ def for_printing(self) -> Tuple[str, str, str, str, str, str]:
77
171
f'`{ self .paper_number } <https://wg21.link/{ self .paper_number } >`__' ,
78
172
self .paper_name ,
79
173
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 () ,
81
175
self .first_released_version if self .first_released_version is not None else '' ,
82
176
' ' .join (f'|{ label } |' for label in self .labels ) if self .labels is not None else '' ,
83
177
)
84
178
85
179
def __repr__ (self ) -> str :
86
180
return repr (self .original ) if self .original is not None else repr (self .for_printing ())
87
181
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
-
95
182
@staticmethod
96
183
def from_csv_row (row : Tuple [str , str , str , str , str , str ]):# -> PaperInfo:
97
184
"""
@@ -105,8 +192,8 @@ def from_csv_row(row: Tuple[str, str, str, str, str, str]):# -> PaperInfo:
105
192
return PaperInfo (
106
193
paper_number = match .group (1 ),
107
194
paper_name = row [1 ],
195
+ status = PaperStatus .from_csv_entry (row [3 ]),
108
196
meeting = row [2 ] or None ,
109
- status = row [3 ] or None ,
110
197
first_released_version = row [4 ] or None ,
111
198
labels = [l .strip ('|' ) for l in row [5 ].split (' ' ) if l ] or None ,
112
199
original = row ,
@@ -123,21 +210,15 @@ def from_github_issue(issue: Dict):# -> PaperInfo:
123
210
raise RuntimeError (f"Issue doesn't have a title that we know how to parse: { issue } " )
124
211
paper = match .group (1 )
125
212
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
-
132
213
# Handle labels
133
214
valid_labels = ('format' , 'ranges' , 'spaceship' , 'flat_containers' , 'concurrency TS' , 'DR' )
134
215
labels = [label for label in issue ['labels' ] if label in valid_labels ]
135
216
136
217
return PaperInfo (
137
218
paper_number = paper ,
138
219
paper_name = issue ['title' ],
220
+ status = PaperStatus .from_github_issue (issue ),
139
221
meeting = issue .get ('meeting Voted' , None ),
140
- status = status ,
141
222
first_released_version = None , # TODO
142
223
labels = labels if labels else None ,
143
224
original = issue ,
@@ -177,30 +258,34 @@ def sync_csv(rows: List[Tuple], from_github: List[PaperInfo]) -> List[Tuple]:
177
258
178
259
paper = PaperInfo .from_csv_row (row )
179
260
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 ]
191
264
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
196
271
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
200
277
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 )
204
289
205
290
return results
206
291
0 commit comments