Skip to content

Instantly share code, notes, and snippets.

@jrichardsz
Created January 31, 2023 16:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jrichardsz/ff8501a17f6e1ebe7021e2896bf69856 to your computer and use it in GitHub Desktop.
Save jrichardsz/ff8501a17f6e1ebe7021e2896bf69856 to your computer and use it in GitHub Desktop.
java coverage badge generator
package coverage;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleBindings;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPathExpressionException;
import org.xml.sax.SAXException;

public class Coverage {

  public static void generateBadge(String templateType) throws IOException, ScriptException,
      XPathExpressionException, SAXException, ParserConfigurationException {
    
    String badgesFolderName = "coverage";

    String baseLocation = Paths.get(".").toAbsolutePath().normalize().toString();

    HashMap<String, HashMap<String, Integer>> metrics =
        getXmlValues(baseLocation + File.separator + "target/site/jacoco/jacoco.xml");

    Class clazz = Coverage.class;
    ClassLoader loader = clazz.getClassLoader();
    String coverageClazzCompiledAbsoluteTargetLocation =
        loader.getResource(clazz.getCanonicalName().replace(".", File.separator) + ".class")
            .getFile().replace(File.separatorChar + "$", "");
    String coverageFolderAbsoluteLocation =
        new File(coverageClazzCompiledAbsoluteTargetLocation).getParent();

    new File(baseLocation + File.separator + badgesFolderName).mkdirs();

    String badgeTemplate = new String(
        Files.readAllBytes(Paths
            .get(coverageFolderAbsoluteLocation + File.separator + templateType + ".template")),
        StandardCharsets.UTF_8);

    for (Entry<String, HashMap<String, Integer>> metric : metrics.entrySet()) {
      // String label, double percentage
      String label = metric.getKey();
      HashMap<String, Integer> metricDetails = metric.getValue();
      double percentage = (metricDetails.get("covered") * 100)
          / (metricDetails.get("covered") + metricDetails.get("missed"));
      String color = null;
      if (percentage == 100d) {
        color = "limegreen";
      } else if (percentage > 80d && percentage <= 99.9d) {
        color = "yellowgreen";
      } else if (percentage > 70d && percentage <= 80d) {
        color = "olivedrab";
      } else if (percentage > 60d && percentage <= 70d) {
        color = "goldenrod";
      } else if (percentage > 50d && percentage <= 60d) {
        color = "darkorange";
      } else if (percentage <= 50d) {
        color = "indianred";
      }

      ScriptEngineManager mgr = new ScriptEngineManager();
      ScriptEngine engine = mgr.getEngineByName("JavaScript");
      Bindings bindings = new SimpleBindings();
      bindings.put("widthA", 70);
      bindings.put("widthB", 50);
      bindings.put("color", color);
      bindings.put("subject", label);
      bindings.put("status", percentage + "%");
      engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
      String evaluatedContent = (String) engine.eval(badgeTemplate);


      Files.write(
          Paths.get(baseLocation + File.separator + badgesFolderName + File.separator + label + ".svg"),
          evaluatedContent.getBytes());
    }


  }

  public static HashMap<String, HashMap<String, Integer>> getXmlValues(String xmlAbsoluteLocation)
      throws IOException {
    String content = new String(Files.readAllBytes(Paths.get(xmlAbsoluteLocation)));
    String lastRawPart = content.substring(content.lastIndexOf("</package>"));
    Pattern pattern = Pattern.compile("<counter.+");
    Pattern patternKeyValue = Pattern.compile("[a-z]+=\"[.\\S]+\"");
    Matcher matcher = pattern.matcher(lastRawPart);
    HashMap<String, HashMap<String, Integer>> metrics = new HashMap<>();
    while (matcher.find()) {
      String rawLine = matcher.group();
      Matcher matcherKeyValue = patternKeyValue.matcher(rawLine);
      HashMap<String, Integer> metric = new HashMap<>();
      String type = null;
      while (matcherKeyValue.find()) {
        String rawKeyValue[] = matcherKeyValue.group().replaceAll("\"", "").split("=");
        if (rawKeyValue[0].contentEquals("type")) {
          type = rawKeyValue[1].toLowerCase();
        } else {
          metric.put(rawKeyValue[0], Integer.parseInt(rawKeyValue[1]));
        }

      }
      metrics.put(type, metric);
    }

    return metrics;
  }


  public static void main(String[] args) throws IOException, ScriptException,
      XPathExpressionException, SAXException, ParserConfigurationException {
    generateBadge("flat");
  }
}

flat template

'\n  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' + (widthA + widthB) + '" height="20">\n    <linearGradient id="smooth" x2="0" y2="100%">\n      <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>\n      <stop offset="1" stop-opacity=".1"/>\n    </linearGradient>\n\n    <mask id="round">\n      <rect width="' + (widthA + widthB) + '" height="20" rx="3" fill="#fff"/>\n    </mask>\n\n    <g mask="url(#round)">\n      <rect width="' + widthA + '" height="20" fill="#555"/>\n      <rect x="' + widthA + '" width="' + widthB + '" height="20" fill="' + (color || '#555') + '"/>\n      <rect width="' + (widthA + widthB) + '" height="20" fill="url(#smooth)"/>\n    </g>\n\n    <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">\n      <text x="' + widthA / 2 + '" y="15" fill="#010101" fill-opacity=".3">' + subject + '</text>\n      <text x="' + widthA / 2 + '" y="14">' + subject + '</text>\n      <text x="' + (widthA + widthB / 2) + '" y="15" fill="#010101" fill-opacity=".3">' + status + '</text>\n      <text x="' + (widthA + widthB / 2) + '" y="14">' + status + '</text>\n    </g>\n  </svg>'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment