def main(fname=None): if not fname: fname = os.path.join(rfcs.root_folder, 'index.md') # Load all metadata all = [rfc for rfc in rfcs.walk()] all.sort(key=lambda x: x.num) tmp_fname = fname + '.tmp' with open(tmp_fname, 'w', encoding='utf-8') as out: out.write("# Aries RFCs by Status\n") for status in rfcs.status_list: out.write(f"\n## [{status}](README.md#{status.lower()})\n") with_status = [rfc for rfc in all if rfc.status == status] for rfc in with_status: line = f"* [{rfc.num}: {rfc.title}]({rfc.relpath})" tags = [f"[`{x}`](/tags.md#{x})" for x in rfc.tags] line += f" ({rfc.since}" if rfc.impl_count: line += f", [{rfc.impl_count} impl" if rfc.impl_count > 1: line += 's' line += '](' + rfc.relpath + '#implementations)' line += ' — ' + ' '.join(tags) + ')' out.write(line + '\n') out.write( "\n\n>(This file is machine-generated; see [code/generate_index.py](code/generate_index.py).)\n" ) update(fname, tmp_fname)
def test_rfc_metadata(): errors = [] def e(rfc, msg): errors.append(rfc.relpath.replace('README.md', '') + ': ' + msg) for rfc in rfcs.walk(): if not bool(rfc.title): e(rfc, 'no title found') if rfc.category not in rfc.relpath: e(rfc, 'category does not match path') if rfc.category[:-1] not in rfc.tags: e(rfc, 'category not in tags') opposite_category = 'feature' if rfc.category == 'concepts' else 'concept' if opposite_category in rfc.tags: e(rfc, 'opposite category in tags') if rfc.status not in rfcs.status_list: e(rfc, 'status is not canonical') if not re.match(r'\d{4}$', rfc.num): e(rfc, 'num is not 4 digits') if not re.search(r'\d{4}-\d{2}-\d{2}', rfc.since): e(rfc, 'since does not contain yyyy-mm-dd') if rfc.start_date: if not re.search(r'\d{4}-\d{2}-\d{2}', rfc.start_date): e(rfc, 'start_date does not contain yyyy-mm-dd') if bool(rfc.authors): if '@' in rfc.authors: if not re.search(r'\[.*?\]\([^)]+@.*?\)', rfc.authors): e(rfc, 'email is not clickable') else: e(rfc, 'no authors found') if ','.join(rfc.tags) != ','.join(rfc.tags).lower(): e(rfc, 'tags are case-sensitive') if rfc.supersedes: if not re.search(r'\[.*?\]\(.*?\)', rfc.supersedes): e(rfc, 'supersedes does not contain hyperlink') if rfc.superseded_by: if not re.search(r'\[.*?\]\(.*?\)', rfc.superseded_by): e(rfc, 'superseded_by does not contain hyperlink') if rfc.impl_count > 0: if rfc.status == 'PROPOSED': e(rfc, 'should not be PROPOSED if it has an impl') if errors: msg = '\n' + '\n'.join(errors) raise BaseException(msg)
def test_rfc_metadata(): errors = [] def e(rfc, msg): errors.append(rfc.relpath.replace('README.md', '') + ': ' + msg) def warn(rfc, msg): sys.stderr.write('Warning: ' + rfc.relpath.replace('README.md', '') + ': ' + msg + '\n') for rfc in rfcs.walk(): if not bool(rfc.title): e(rfc, 'no title found') if rfc.category not in rfc.relpath: e(rfc, 'category does not match path') if rfc.category[:-1] not in rfc.tags: e(rfc, 'category not in tags') opposite_category = 'feature' if rfc.category == 'concepts' else 'concept' if opposite_category in rfc.tags: e(rfc, 'opposite category in tags') if rfc.status not in rfcs.status_list: e(rfc, 'status is not canonical') if not re.match(r'\d{4}$', rfc.num): e(rfc, 'num is not 4 digits') if not re.search(r'\d{4}-\d{2}-\d{2}', rfc.since): e(rfc, 'since does not contain yyyy-mm-dd') if rfc.start_date: if not re.search(r'\d{4}-\d{2}-\d{2}', rfc.start_date): e(rfc, 'start_date does not contain yyyy-mm-dd') if bool(rfc.authors): if '@' in rfc.authors: if not re.search(r'\[.*?\]\([^)]+@.*?\)', rfc.authors): e(rfc, 'email is not clickable') else: e(rfc, 'no authors found') if ','.join(rfc.tags) != ','.join(rfc.tags).lower(): e(rfc, 'tags are case-sensitive') if rfc.supersedes: if not re.search(r'\[.*?\]\(.*?\)', rfc.supersedes): e(rfc, 'supersedes does not contain hyperlink') if rfc.superseded_by: if not re.search(r'\[.*?\]\(.*?\)', rfc.superseded_by): e(rfc, 'superseded_by does not contain hyperlink') if rfc.status == 'PROPOSED': for impl in rfcs.test_suite_impls(rfc, False): e(rfc, 'should not be PROPOSED if it has a non-test-suite impl') break # Should this RFC have links to test results? elif rfc.status in [ 'ACCEPTED', 'ADOPTED' ] and 'feature' in rfc.tags and ('protocol' in rfc.tags or 'decorator' in rfc.tags): found_test_suite_in_impls = False for row in rfcs.test_suite_impls(rfc, True): found_test_suite_in_impls = True break if not found_test_suite_in_impls: msg = 'Test suite must be an impl for any protocol- or decorator-related RFC beyond DEMONSTRATED status.' if 'test-anomaly' in rfc.tags: warn(rfc, msg) else: e(rfc, msg + ' Tag "test-anomaly" to temporarily override.') for row in rfcs.test_suite_impls(rfc, False): m = rfcs.get_test_results_link(row) # If we lack a link entirely, this is an error, period. # If we have tagged the RFC with "test-anomaly", then it becomes possible to link # the ugly text "MISSING test results" to the test-anomaly tag and have the result # be only a warning. This ugly text+link should only be accepted when the 'test-anomaly' # tag is present. desc = rfcs.describe_impl_row(row) if m is None: e( rfc, 'Impl "%s" needs a link to test results in its Notes column. Format = [test results](...) or, if RFC is tagged "test-anomaly", [MISSING test results](/tags.md#test-anomaly).' % desc) # Are test results explicitly declared to be missing? elif ('MISSING' in m.group(1) and '/tags.md#test-anomaly' in m.group(2)): if 'test-anomaly' in rfc.tags: warn( rfc, 'Impl "%s" needs to replace missing test results with something meaningful.' % desc) else: e( rfc, 'Can\'t declare missing tests without the "test-anomaly" tag to make the RFC ugly, so impl "%s" needs a link to test results in its Notes column. Format = [test results](...).' % desc) if errors: msg = '\n' + '\n'.join(errors) raise BaseException(msg)