garbage_code_hunter/badge/
generator.rs1#[derive(Debug, Clone, Default, PartialEq)]
5pub enum BadgeStyle {
6 #[default]
7 Flat,
8 Plastic,
9}
10
11fn score_color(score: f64) -> &'static str {
13 match score as u32 {
14 90..=100 => "#4c1",
15 70..=89 => "#97CA00",
16 50..=69 => "#dfb317",
17 30..=49 => "#fe7d37",
18 _ => "#e05d44",
19 }
20}
21
22pub fn generate_svg(score: f64, style: &BadgeStyle) -> String {
24 let label = "garbage";
25 let score_str = format!("{:.0}", score);
26 let color = score_color(score);
27
28 let label_width: u32 = 75;
29 let score_width: u32 = 85.max(score_str.len() as u32 * 10 + 20);
30 let total_width = label_width + score_width;
31 let label_center = label_width / 2;
32 let score_center = label_width + score_width / 2;
33
34 match style {
35 BadgeStyle::Flat => {
36 let mut s = String::new();
37 s.push_str(&format!(
38 "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{}\" height=\"20\">\n",
39 total_width
40 ));
41 s.push_str(" <linearGradient id=\"b\" x2=\"0\" y2=\"100%\">\n");
42 s.push_str(" <stop offset=\"0\" stop-color=\"#bbb\" stop-opacity=\".1\"/>\n");
43 s.push_str(" <stop offset=\"1\" stop-opacity=\".1\"/>\n");
44 s.push_str(" </linearGradient>\n");
45 s.push_str(&format!(
46 " <mask id=\"a\"><rect width=\"{}\" height=\"20\" rx=\"3\" fill=\"#fff\"/></mask>\n",
47 total_width
48 ));
49 s.push_str(" <g mask=\"url(#a)\">\n");
50 s.push_str(&format!(
51 " <rect width=\"{}\" height=\"20\" fill=\"#555\"/>\n",
52 label_width
53 ));
54 s.push_str(&format!(
55 " <rect x=\"{}\" width=\"{}\" height=\"20\" fill=\"{}\"/>\n",
56 label_width, score_width, color
57 ));
58 s.push_str(&format!(
59 " <rect width=\"{}\" height=\"20\" fill=\"url(#b)\"/>\n",
60 total_width
61 ));
62 s.push_str(" </g>\n");
63 s.push_str(" <g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">\n");
64 s.push_str(&format!(
65 " <text x=\"{}\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">{}</text>\n",
66 label_center, label
67 ));
68 s.push_str(&format!(
69 " <text x=\"{}\" y=\"14\">{}</text>\n",
70 label_center, label
71 ));
72 s.push_str(&format!(
73 " <text x=\"{}\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">{}</text>\n",
74 score_center, score_str
75 ));
76 s.push_str(&format!(
77 " <text x=\"{}\" y=\"14\">{}</text>\n",
78 score_center, score_str
79 ));
80 s.push_str(" </g>\n");
81 s.push_str("</svg>");
82 s
83 }
84 BadgeStyle::Plastic => {
85 let mut s = String::new();
86 s.push_str(&format!(
87 "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"{}\" height=\"20\">\n",
88 total_width
89 ));
90 s.push_str(" <linearGradient id=\"a\" x2=\"0\" y2=\"100%\">\n");
92 s.push_str(" <stop offset=\"0\" stop-color=\"#fff\" stop-opacity=\".35\"/>\n");
93 s.push_str(" <stop offset=\"1\" stop-opacity=\".15\"/>\n");
94 s.push_str(" </linearGradient>\n");
95 s.push_str(" <linearGradient id=\"b\" x2=\"0\" y2=\"100%\">\n");
97 s.push_str(" <stop offset=\"0\" stop-color=\"#000\" stop-opacity=\".12\"/>\n");
98 s.push_str(" <stop offset=\"1\" stop-opacity=\".25\"/>\n");
99 s.push_str(" </linearGradient>\n");
100 s.push_str(&format!(
101 " <mask id=\"m\"><rect width=\"{}\" height=\"20\" rx=\"5\" fill=\"#fff\"/></mask>\n",
102 total_width
103 ));
104 s.push_str(" <g mask=\"url(#m)\">\n");
105 s.push_str(&format!(
107 " <rect width=\"{}\" height=\"20\" fill=\"#555\"/>\n",
108 label_width
109 ));
110 s.push_str(&format!(
111 " <rect x=\"{}\" width=\"{}\" height=\"20\" fill=\"{}\"/>\n",
112 label_width, score_width, color
113 ));
114 s.push_str(&format!(
116 " <rect width=\"{}\" height=\"10\" fill=\"url(#a)\"/>\n",
117 total_width
118 ));
119 s.push_str(&format!(
121 " <rect y=\"10\" width=\"{}\" height=\"10\" fill=\"url(#b)\"/>\n",
122 total_width
123 ));
124 s.push_str(" </g>\n");
125 s.push_str(&format!(
127 " <rect width=\"{}\" height=\"1\" fill=\"#fff\" fill-opacity=\".3\" rx=\"5\"/>\n",
128 total_width
129 ));
130 s.push_str(" <g fill=\"#fff\" text-anchor=\"middle\" font-family=\"DejaVu Sans,Verdana,Geneva,sans-serif\" font-size=\"11\">\n");
131 s.push_str(&format!(
132 " <text x=\"{}\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">{}</text>\n",
133 label_center, label
134 ));
135 s.push_str(&format!(
136 " <text x=\"{}\" y=\"14\">{}</text>\n",
137 label_center, label
138 ));
139 s.push_str(&format!(
140 " <text x=\"{}\" y=\"15\" fill=\"#010101\" fill-opacity=\".3\">{}</text>\n",
141 score_center, score_str
142 ));
143 s.push_str(&format!(
144 " <text x=\"{}\" y=\"14\">{}</text>\n",
145 score_center, score_str
146 ));
147 s.push_str(" </g>\n");
148 s.push_str("</svg>");
149 s
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn test_score_color_ranges() {
160 assert_eq!(score_color(95.0), "#4c1");
161 assert_eq!(score_color(80.0), "#97CA00");
162 assert_eq!(score_color(60.0), "#dfb317");
163 assert_eq!(score_color(40.0), "#fe7d37");
164 assert_eq!(score_color(20.0), "#e05d44");
165 }
166
167 #[test]
168 fn test_generate_flat_contains_svg() {
169 let svg = generate_svg(75.0, &BadgeStyle::Flat);
170 assert!(svg.contains("<svg"));
171 assert!(svg.contains("</svg>"));
172 assert!(svg.contains("garbage"));
173 assert!(svg.contains("75"));
174 }
175
176 #[test]
177 fn test_generate_plastic_contains_svg() {
178 let svg = generate_svg(90.0, &BadgeStyle::Plastic);
179 assert!(svg.contains("<svg"));
180 assert!(svg.contains("</svg>"));
181 assert!(svg.contains("90"));
182 }
183
184 #[test]
185 fn test_badge_uses_correct_color() {
186 let svg = generate_svg(95.0, &BadgeStyle::Flat);
187 assert!(svg.contains("#4c1"));
188
189 let svg = generate_svg(25.0, &BadgeStyle::Flat);
190 assert!(svg.contains("#e05d44"));
191 }
192
193 #[test]
194 fn test_badge_score_formatting() {
195 let svg = generate_svg(72.8, &BadgeStyle::Flat);
196 assert!(svg.contains("73")); }
198}