Skip to content

Commit 7f25f2d

Browse files
aksOpsclaude
andcommitted
Add GitHub Actions for CI and dynamic README badge updates
- ci.yml: runs pytest on Python 3.11 and 3.12 on push/PR to main - update-readme-badges.yml: auto-updates detectors, languages, tests, files, and LOC badges in README when src/tests change - scripts/update_readme_badges.py: collects live stats and rewrites badge HTML between DYNAMIC marker comments - README badges now have DYNAMIC markers for automated updates Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6ea2081 commit 7f25f2d

4 files changed

Lines changed: 205 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.11", "3.12"]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
24+
- name: Install dependencies
25+
run: pip install -e ".[dev]"
26+
27+
- name: Run tests
28+
run: pytest --tb=short -q
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Update README Badges
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "src/**"
8+
- "tests/**"
9+
- "pyproject.toml"
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: write
14+
15+
jobs:
16+
update-badges:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Set up Python
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: "3.12"
25+
26+
- name: Install dependencies
27+
run: pip install -e ".[dev]"
28+
29+
- name: Update README badges
30+
run: python scripts/update_readme_badges.py
31+
32+
- name: Check for changes
33+
id: changes
34+
run: |
35+
git diff --quiet README.md && echo "changed=false" >> "$GITHUB_OUTPUT" || echo "changed=true" >> "$GITHUB_OUTPUT"
36+
37+
- name: Commit and push
38+
if: steps.changes.outputs.changed == 'true'
39+
run: |
40+
git config user.name "github-actions[bot]"
41+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
42+
git add README.md
43+
git commit -m "chore: update README badges [skip ci]"
44+
git push

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
<a href="https://github.com/RandomCodeSpace/code-iq/pulls"><img src="https://img.shields.io/github/issues-pr/RandomCodeSpace/code-iq?style=flat-square&logo=github&label=PRs" alt="Pull Requests"></a>
1717
<a href="https://github.com/RandomCodeSpace/code-iq/commits/main"><img src="https://img.shields.io/github/last-commit/RandomCodeSpace/code-iq?style=flat-square&logo=github&label=Last%20Commit" alt="Last Commit"></a>
1818
<a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/github/repo-size/RandomCodeSpace/code-iq?style=flat-square&logo=github&label=Repo%20Size" alt="Repo Size"></a>
19-
<a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/detectors-58-brightgreen?style=flat-square&logo=codefactor&logoColor=white" alt="58 Detectors"></a>
20-
<a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/languages-33-blue?style=flat-square&logo=stackblitz&logoColor=white" alt="33 Languages"></a>
21-
<a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/tests-113%20passed-brightgreen?style=flat-square&logo=pytest&logoColor=white" alt="113 Tests"></a>
22-
<a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/files-137-informational?style=flat-square&logo=files&logoColor=white" alt="137 Files"></a>
23-
<a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/LOC-15%2C277-informational?style=flat-square&logo=codacy&logoColor=white" alt="15,277 LOC"></a>
19+
<!-- DYNAMIC:detectors --><a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/detectors-58-brightgreen?style=flat-square&logo=codefactor&logoColor=white" alt="58 Detectors"></a><!-- /DYNAMIC:detectors -->
20+
<!-- DYNAMIC:languages --><a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/languages-33-blue?style=flat-square&logo=stackblitz&logoColor=white" alt="33 Languages"></a><!-- /DYNAMIC:languages -->
21+
<!-- DYNAMIC:tests --><a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/tests-113%20passed-brightgreen?style=flat-square&logo=pytest&logoColor=white" alt="113 passed Tests"></a><!-- /DYNAMIC:tests -->
22+
<!-- DYNAMIC:files --><a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/files-137-informational?style=flat-square&logo=files&logoColor=white" alt="137 Files"></a><!-- /DYNAMIC:files -->
23+
<!-- DYNAMIC:loc --><a href="https://github.com/RandomCodeSpace/code-iq"><img src="https://img.shields.io/badge/LOC-15%2C277-informational?style=flat-square&logo=codacy&logoColor=white" alt="15,277 Loc"></a><!-- /DYNAMIC:loc -->
2424
</p>
2525

2626
---

scripts/update_readme_badges.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/usr/bin/env python3
2+
"""Update dynamic badges in README.md with current project stats."""
3+
4+
from __future__ import annotations
5+
6+
import re
7+
import subprocess
8+
import sys
9+
from pathlib import Path
10+
from urllib.parse import quote
11+
12+
ROOT = Path(__file__).resolve().parent.parent
13+
README = ROOT / "README.md"
14+
15+
16+
def _run(cmd: list[str], **kwargs) -> str:
17+
return subprocess.check_output(cmd, cwd=ROOT, text=True, **kwargs).strip()
18+
19+
20+
def count_files() -> int:
21+
src = list((ROOT / "src").rglob("*.py"))
22+
tests = list((ROOT / "tests").rglob("*.py"))
23+
return len(src) + len(tests)
24+
25+
26+
def count_loc() -> int:
27+
total = 0
28+
for d in ("src", "tests"):
29+
for f in (ROOT / d).rglob("*.py"):
30+
total += sum(1 for _ in f.open())
31+
return total
32+
33+
34+
def count_tests() -> int:
35+
try:
36+
out = _run(
37+
[sys.executable, "-m", "pytest", "tests/", "-q", "--tb=no", "--no-header"],
38+
stderr=subprocess.STDOUT,
39+
)
40+
except subprocess.CalledProcessError as e:
41+
out = e.output
42+
# Match "113 passed" from pytest output
43+
m = re.search(r"(\d+) passed", out)
44+
return int(m.group(1)) if m else 0
45+
46+
47+
def count_detectors_and_languages() -> tuple[int, int]:
48+
out = _run([
49+
sys.executable, "-c",
50+
"from code_intelligence.detectors.registry import DetectorRegistry; "
51+
"r = DetectorRegistry(); r.load_builtin_detectors(); "
52+
"langs = {l for d in r.all_detectors() for l in d.supported_languages}; "
53+
"print(len(r.all_detectors()), len(langs))",
54+
])
55+
parts = out.strip().split()
56+
return int(parts[0]), int(parts[1])
57+
58+
59+
def fmt(n: int) -> str:
60+
"""Format number with commas for display, URL-encoded."""
61+
return f"{n:,}"
62+
63+
64+
def badge(label: str, value: str, color: str, logo: str) -> str:
65+
"""Generate a shields.io badge HTML snippet."""
66+
val_encoded = quote(value, safe="")
67+
return (
68+
f'<a href="https://github.com/RandomCodeSpace/code-iq">'
69+
f'<img src="https://img.shields.io/badge/{label}-{val_encoded}-{color}'
70+
f'?style=flat-square&logo={logo}&logoColor=white" alt="{value} {label.capitalize()}">'
71+
f"</a>"
72+
)
73+
74+
75+
def update_badge(content: str, name: str, new_badge: str) -> str:
76+
"""Replace content between DYNAMIC markers."""
77+
pattern = rf"(<!-- DYNAMIC:{name} -->).*?(<!-- /DYNAMIC:{name} -->)"
78+
replacement = rf"\g<1>{new_badge}\g<2>"
79+
return re.sub(pattern, replacement, content)
80+
81+
82+
def main() -> None:
83+
print("Collecting project stats...")
84+
85+
files = count_files()
86+
loc = count_loc()
87+
tests = count_tests()
88+
detectors, languages = count_detectors_and_languages()
89+
90+
print(f" Files: {files}")
91+
print(f" LOC: {loc:,}")
92+
print(f" Tests: {tests}")
93+
print(f" Detectors: {detectors}")
94+
print(f" Languages: {languages}")
95+
96+
content = README.read_text()
97+
original = content
98+
99+
content = update_badge(
100+
content, "detectors",
101+
badge("detectors", str(detectors), "brightgreen", "codefactor"),
102+
)
103+
content = update_badge(
104+
content, "languages",
105+
badge("languages", str(languages), "blue", "stackblitz"),
106+
)
107+
content = update_badge(
108+
content, "tests",
109+
badge("tests", f"{tests} passed", "brightgreen", "pytest"),
110+
)
111+
content = update_badge(
112+
content, "files",
113+
badge("files", str(files), "informational", "files"),
114+
)
115+
content = update_badge(
116+
content, "loc",
117+
badge("LOC", fmt(loc), "informational", "codacy"),
118+
)
119+
120+
if content != original:
121+
README.write_text(content)
122+
print("README.md updated.")
123+
else:
124+
print("No changes needed.")
125+
126+
127+
if __name__ == "__main__":
128+
main()

0 commit comments

Comments
 (0)