mirror of
https://github.com/wilsonfreitas/awesome-quant.git
synced 2026-08-20 23:38:07 +00:00
handle updated entries in PR reviews
This commit is contained in:
+59
-10
@@ -177,16 +177,37 @@ def read_readme(repository: Any, ref: str) -> str:
|
|||||||
return content.decoded_content.decode("utf-8")
|
return content.decoded_content.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def entries_represent_same_project(old_line: str, new_line: str) -> bool:
|
||||||
|
old_match = ENTRY_RE.match(old_line)
|
||||||
|
new_match = ENTRY_RE.match(new_line)
|
||||||
|
if old_match is None or new_match is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if normalize(old_match.group(1)) == normalize(new_match.group(1)):
|
||||||
|
return True
|
||||||
|
|
||||||
|
old_urls = {
|
||||||
|
canonicalize_url(url) for url in MARKDOWN_URL_RE.findall(old_line)
|
||||||
|
}
|
||||||
|
new_urls = {
|
||||||
|
canonicalize_url(url) for url in MARKDOWN_URL_RE.findall(new_line)
|
||||||
|
}
|
||||||
|
return bool(old_urls & new_urls)
|
||||||
|
|
||||||
|
|
||||||
def analyze_readme_change(
|
def analyze_readme_change(
|
||||||
base_readme: str,
|
base_readme: str,
|
||||||
head_readme: str,
|
head_readme: str,
|
||||||
) -> tuple[str | None, list[Finding]]:
|
) -> tuple[str | None, str | None, list[Finding]]:
|
||||||
added_lines, removed_lines = readme_changed_lines(base_readme, head_readme)
|
added_lines, removed_lines = readme_changed_lines(base_readme, head_readme)
|
||||||
substantive_added = [line for line in added_lines if line.strip()]
|
substantive_added = [line for line in added_lines if line.strip()]
|
||||||
substantive_removed = [line for line in removed_lines if line.strip()]
|
substantive_removed = [line for line in removed_lines if line.strip()]
|
||||||
entry_lines = [
|
entry_lines = [
|
||||||
line for line in substantive_added if line.strip().startswith("- ")
|
line for line in substantive_added if line.strip().startswith("- ")
|
||||||
]
|
]
|
||||||
|
removed_entry_lines = [
|
||||||
|
line for line in substantive_removed if line.strip().startswith("- ")
|
||||||
|
]
|
||||||
findings: list[Finding] = []
|
findings: list[Finding] = []
|
||||||
if len(entry_lines) != 1:
|
if len(entry_lines) != 1:
|
||||||
findings.append(
|
findings.append(
|
||||||
@@ -195,19 +216,39 @@ def analyze_readme_change(
|
|||||||
"expected exactly one added README entry line",
|
"expected exactly one added README entry line",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return None, findings
|
return None, None, findings
|
||||||
|
|
||||||
unauthorized_additions = [
|
unauthorized_additions = [
|
||||||
line for line in substantive_added if line != entry_lines[0]
|
line for line in substantive_added if line != entry_lines[0]
|
||||||
]
|
]
|
||||||
if substantive_removed or unauthorized_additions:
|
unauthorized_removals = [
|
||||||
|
line for line in substantive_removed if line not in removed_entry_lines
|
||||||
|
]
|
||||||
|
removed_entry_line = (
|
||||||
|
removed_entry_lines[0] if len(removed_entry_lines) == 1 else None
|
||||||
|
)
|
||||||
|
invalid_update = bool(substantive_removed) and (
|
||||||
|
removed_entry_line is None
|
||||||
|
or bool(unauthorized_removals)
|
||||||
|
or not entries_represent_same_project(removed_entry_line, entry_lines[0])
|
||||||
|
)
|
||||||
|
if unauthorized_additions or invalid_update:
|
||||||
findings.append(
|
findings.append(
|
||||||
Finding(
|
Finding(
|
||||||
"content",
|
"content",
|
||||||
"README changes must add one entry without other substantive edits",
|
"README changes must add one entry or update the same project "
|
||||||
|
"without other substantive edits",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return entry_lines[0], findings
|
return entry_lines[0], removed_entry_line, findings
|
||||||
|
|
||||||
|
|
||||||
|
def remove_entry_line(readme_text: str, entry_line: str | None) -> str:
|
||||||
|
if entry_line is None:
|
||||||
|
return readme_text
|
||||||
|
lines = readme_text.splitlines()
|
||||||
|
lines.remove(entry_line)
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def readme_changed_lines(
|
def readme_changed_lines(
|
||||||
@@ -474,7 +515,10 @@ def review_pr(
|
|||||||
|
|
||||||
base_readme = read_readme(repository, pull_request.base.sha)
|
base_readme = read_readme(repository, pull_request.base.sha)
|
||||||
head_readme = read_readme(repository, pull_request.head.sha)
|
head_readme = read_readme(repository, pull_request.head.sha)
|
||||||
entry_line, change_findings = analyze_readme_change(base_readme, head_readme)
|
entry_line, removed_entry_line, change_findings = analyze_readme_change(
|
||||||
|
base_readme,
|
||||||
|
head_readme,
|
||||||
|
)
|
||||||
findings.extend(change_findings)
|
findings.extend(change_findings)
|
||||||
if entry_line is None:
|
if entry_line is None:
|
||||||
return findings, pull_request.title
|
return findings, pull_request.title
|
||||||
@@ -545,9 +589,13 @@ def review_pr(
|
|||||||
github_urls.extend(GITHUB_LINK_RE.findall(line))
|
github_urls.extend(GITHUB_LINK_RE.findall(line))
|
||||||
github_urls = list(dict.fromkeys(github_urls))
|
github_urls = list(dict.fromkeys(github_urls))
|
||||||
if not github_urls:
|
if not github_urls:
|
||||||
findings.append(
|
if section != "Commercial & Proprietary Services":
|
||||||
Finding("github", "no GitHub repository URL found; cannot verify activity")
|
findings.append(
|
||||||
)
|
Finding(
|
||||||
|
"github",
|
||||||
|
"no GitHub repository URL found; cannot verify activity",
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
repository_parts = parse_github_repository_url(github_urls[0])
|
repository_parts = parse_github_repository_url(github_urls[0])
|
||||||
if not repository_parts:
|
if not repository_parts:
|
||||||
@@ -587,7 +635,8 @@ def review_pr(
|
|||||||
if not primary_github and not url_reachable(url):
|
if not primary_github and not url_reachable(url):
|
||||||
findings.append(Finding("reachability", f"primary URL is not reachable: {url}"))
|
findings.append(Finding("reachability", f"primary URL is not reachable: {url}"))
|
||||||
|
|
||||||
if readme_has_duplicate(base_readme, name, [url, *github_urls]):
|
duplicate_base_readme = remove_entry_line(base_readme, removed_entry_line)
|
||||||
|
if readme_has_duplicate(duplicate_base_readme, name, [url, *github_urls]):
|
||||||
findings.append(
|
findings.append(
|
||||||
Finding("duplicates", "project name or URL already exists in README.md")
|
Finding("duplicates", "project name or URL already exists in README.md")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -662,6 +662,90 @@ class ValidationPipelineTests(unittest.TestCase):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_accepts_description_update_for_the_same_project(self):
|
||||||
|
base_readme = (
|
||||||
|
"# awesome-quant\n\n## Trading & Backtesting\n"
|
||||||
|
"- [Fresh](https://github.com/example/fresh) - `Python` - Old description.\n"
|
||||||
|
)
|
||||||
|
head_readme = base_readme.replace("Old description.", "New description.")
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.review(base_readme=base_readme, head_readme=head_readme),
|
||||||
|
set(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_accepts_tag_update_for_the_same_project(self):
|
||||||
|
base_readme = (
|
||||||
|
"# awesome-quant\n\n## Trading & Backtesting\n"
|
||||||
|
"- [Fresh](https://github.com/example/fresh) - `Python` - Fresh project.\n"
|
||||||
|
)
|
||||||
|
head_readme = base_readme.replace("`Python`", "`Python` `Rust`")
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.review(base_readme=base_readme, head_readme=head_readme),
|
||||||
|
set(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_accepts_url_update_when_the_name_is_unchanged(self):
|
||||||
|
base_readme = (
|
||||||
|
"# awesome-quant\n\n## Trading & Backtesting\n"
|
||||||
|
"- [Fresh](https://old.example.com/fresh) - `Python` - Fresh project. "
|
||||||
|
"[GitHub](https://github.com/example/fresh)\n"
|
||||||
|
)
|
||||||
|
head_readme = base_readme.replace(
|
||||||
|
"https://old.example.com/fresh",
|
||||||
|
"https://new.example.com/fresh",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.review(base_readme=base_readme, head_readme=head_readme),
|
||||||
|
set(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_accepts_rename_when_the_repository_is_unchanged(self):
|
||||||
|
base_readme = (
|
||||||
|
"# awesome-quant\n\n## Trading & Backtesting\n"
|
||||||
|
"- [Old Name](https://github.com/example/fresh) - `Python` - Fresh project.\n"
|
||||||
|
)
|
||||||
|
head_readme = base_readme.replace("[Old Name]", "[Fresh]")
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.review(base_readme=base_readme, head_readme=head_readme),
|
||||||
|
set(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_accepts_section_move_for_the_same_project(self):
|
||||||
|
base_readme = (
|
||||||
|
"# awesome-quant\n\n## Trading & Backtesting\n"
|
||||||
|
"- [Fresh](https://github.com/example/fresh) - `Python` - Fresh project.\n"
|
||||||
|
"\n## Portfolio Optimization & Risk Analysis\n"
|
||||||
|
)
|
||||||
|
head_readme = (
|
||||||
|
"# awesome-quant\n\n## Trading & Backtesting\n"
|
||||||
|
"\n## Portfolio Optimization & Risk Analysis\n"
|
||||||
|
"- [Fresh](https://github.com/example/fresh) - `Python` - Fresh project.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.review(base_readme=base_readme, head_readme=head_readme),
|
||||||
|
set(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rejects_replacing_an_entry_with_an_unrelated_project(self):
|
||||||
|
base_readme = (
|
||||||
|
"# awesome-quant\n\n## Trading & Backtesting\n"
|
||||||
|
"- [Old](https://github.com/example/old) - `Python` - Old project.\n"
|
||||||
|
)
|
||||||
|
head_readme = (
|
||||||
|
"# awesome-quant\n\n## Trading & Backtesting\n"
|
||||||
|
"- [Fresh](https://github.com/example/fresh) - `Python` - Fresh project.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn(
|
||||||
|
"content",
|
||||||
|
self.review(base_readme=base_readme, head_readme=head_readme),
|
||||||
|
)
|
||||||
|
|
||||||
def test_rejects_deleting_an_existing_entry(self):
|
def test_rejects_deleting_an_existing_entry(self):
|
||||||
base_readme = (
|
base_readme = (
|
||||||
"# awesome-quant\n\n"
|
"# awesome-quant\n\n"
|
||||||
@@ -790,6 +874,19 @@ class ValidationPipelineTests(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
self.assertIn("github-link", self.review(patch_text=patch_text))
|
self.assertIn("github-link", self.review(patch_text=patch_text))
|
||||||
|
|
||||||
|
def test_accepts_commercial_entry_without_github_repository(self):
|
||||||
|
patch_text = """@@ -1,1 +1,2 @@
|
||||||
|
## Commercial & Proprietary Services
|
||||||
|
+- [Fresh](https://example.com/fresh) - Commercial risk calculator.
|
||||||
|
"""
|
||||||
|
|
||||||
|
base_readme = "# awesome-quant\n\n## Commercial & Proprietary Services\n"
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
self.review(patch_text=patch_text, base_readme=base_readme),
|
||||||
|
set(),
|
||||||
|
)
|
||||||
|
|
||||||
def test_rejects_entry_without_github_repository(self):
|
def test_rejects_entry_without_github_repository(self):
|
||||||
patch_text = VALID_PATCH.replace(
|
patch_text = VALID_PATCH.replace(
|
||||||
"https://github.com/example/fresh",
|
"https://github.com/example/fresh",
|
||||||
|
|||||||
Reference in New Issue
Block a user