Coverage for src/pytribeam/cicd/report_badges.py: 49%
53 statements
« prev ^ index » next coverage.py v7.6.1, created at 2026-06-16 18:30 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2026-06-16 18:30 +0000
1#!/usr/bin/env python3
2"""
3Generates SVG badges for Lint and Coverage scores.
4"""
6import argparse
7import os
8import re
9import xml.etree.ElementTree as ET
11from pytribeam.cicd.utilities import (
12 get_score_color_coverage,
13 get_score_color_lint,
14)
17def get_pylint_score(input_file: str) -> str:
18 """Extract score from pylint report text."""
19 if not os.path.exists(input_file): 19 ↛ 20line 19 didn't jump to line 20 because the condition on line 19 was never true
20 return "0.00"
21 with open(input_file, "r", encoding="utf-8") as f:
22 content = f.read()
23 match = re.search(r"Your code has been rated at (\d+\.\d+)/10", content)
24 return match.group(1) if match else "0.00"
27def get_coverage_score(input_file: str) -> str:
28 """Extract percentage from coverage XML."""
29 if not os.path.exists(input_file): 29 ↛ 30line 29 didn't jump to line 30 because the condition on line 29 was never true
30 return "0.0"
31 try:
32 tree = ET.parse(input_file)
33 root = tree.getroot()
34 lines_valid = float(root.attrib.get("lines-valid", 0))
35 lines_covered = float(root.attrib.get("lines-covered", 0))
36 if lines_valid == 0: 36 ↛ 37line 36 didn't jump to line 37 because the condition on line 36 was never true
37 return "0.0"
38 return f"{(lines_covered / lines_valid * 100):.1f}"
39 except Exception:
40 return "0.0"
43def generate_badge_svg(label: str, value: str, color_key: str) -> str:
44 """Generate a flat-style SVG badge."""
45 # Map color keys to hex
46 color_map = {
47 "brightgreen": "#4c1",
48 "green": "#97ca00",
49 "yellow": "#dfb317",
50 "orange": "#fe7d37",
51 "red": "#e05d44",
52 "gray": "#9f9f9f",
53 }
54 color = color_map.get(color_key, color_map["gray"])
56 # Simple width calculation
57 label_w = len(label) * 7 + 10
58 value_w = len(value) * 7 + 10
59 total_w = label_w + value_w
61 return f"""<svg xmlns="http://www.w3.org/2000/svg" width="{total_w}" height="20" viewBox="0 0 {total_w} 20" preserveAspectRatio="xMidYMid meet">
62 <linearGradient id="b" x2="0" y2="100%">
63 <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
64 <stop offset="1" stop-opacity=".1"/>
65 </linearGradient>
66 <mask id="a">
67 <rect width="{total_w}" height="20" rx="3" fill="#fff"/>
68 </mask>
69 <g mask="url(#a)">
70 <path fill="#555" d="M0 0h{label_w}v20H0z"/>
71 <path fill="{color}" d="M{label_w} 0h{value_w}v20H{label_w}z"/>
72 <path fill="url(#b)" d="M0 0h{total_w}v20H0z"/>
73 </g>
74 <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">
75 <text x="{label_w / 2}" y="15" fill="#010101" fill-opacity=".3">{label}</text>
76 <text x="{label_w / 2}" y="14">{label}</text>
77 <text x="{label_w + value_w / 2}" y="15" fill="#010101" fill-opacity=".3">{value}</text>
78 <text x="{label_w + value_w / 2}" y="14">{value}</text>
79 </g>
80 </svg>"""
83def main():
84 parser = argparse.ArgumentParser(
85 description="Generate SVG badges for CI/CD results."
86 )
87 parser.add_argument("--lint_file", help="Path to pylint report text file")
88 parser.add_argument("--coverage_file", help="Path to coverage XML file")
89 parser.add_argument("--output_dir", required=True, help="Directory to save badges")
91 args = parser.parse_args()
92 os.makedirs(args.output_dir, exist_ok=True)
94 if args.lint_file:
95 score = get_pylint_score(args.lint_file)
96 color = get_score_color_lint(score)
97 svg = generate_badge_svg("lint", f"{score}/10", color)
98 with open(os.path.join(args.output_dir, "lint.svg"), "w") as f:
99 f.write(svg)
100 print(f"[OK] Lint badge generated: {score}/10")
102 if args.coverage_file:
103 score = get_coverage_score(args.coverage_file)
104 color = get_score_color_coverage(score)
105 svg = generate_badge_svg("coverage", f"{score}%", color)
106 with open(os.path.join(args.output_dir, "coverage.svg"), "w") as f:
107 f.write(svg)
108 print(f"[OK] Coverage badge generated: {score}%")
111if __name__ == "__main__":
112 main()