diff --git a/pom.xml b/pom.xml
index 159aead..ed3caec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
net.ftlines
wicket-css-scope
- 2.1.0-SNAPSHOT
+ 2.1.1-SNAPSHOT
maven-plugin
42 Lines - Wicket Css Scoping
diff --git a/src/main/java/net/ftlines/css/scoper/maven/CompileIconSelection.java b/src/main/java/net/ftlines/css/scoper/maven/CompileIconSelection.java
new file mode 100644
index 0000000..2d42b1e
--- /dev/null
+++ b/src/main/java/net/ftlines/css/scoper/maven/CompileIconSelection.java
@@ -0,0 +1,166 @@
+package net.ftlines.css.scoper.maven;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+
+@Mojo(name = "compile-icons", defaultPhase = LifecyclePhase.GENERATE_RESOURCES)
+public class CompileIconSelection extends AbstractMojo {
+
+ @Parameter(required = true)
+ public File outputMixinsFile;
+
+ @Parameter(required = true)
+ public File outputIconsFile;
+
+ @Parameter(required = true)
+ public File inputFile;
+
+ @Parameter(required = true)
+ public File inputMixinsTemplateFilePath;
+
+ @Parameter(required = true)
+ public File inputIconsTemplateFilePath;
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ try(FileWriter writer = new FileWriter(outputMixinsFile)) {
+ IOUtils.write(renderMixinsTemplate(), writer);
+ writer.flush();
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error compiling icons", e);
+ }
+
+ try(FileWriter writer = new FileWriter(outputIconsFile)) {
+ IOUtils.write(renderIconsTemplate(), writer);
+ writer.flush();
+ } catch (Exception e) {
+ throw new MojoExecutionException("Error compiling icons", e);
+ }
+ }
+
+ private List> getIcons() throws Exception {
+ return getIcons(inputFile);
+ }
+
+ protected static List> getIcons(File inputFile) throws Exception {
+ try (FileReader reader = new FileReader(inputFile)) {
+ JsonElement elem = new Gson().fromJson(reader, JsonElement.class);
+ JsonArray iconArray = elem.getAsJsonObject().get("icons").getAsJsonArray();
+ List> list = new ArrayList>();
+
+ Consumer sink = (z) -> {
+ convertIcon(z, list::add);
+ };
+
+ iconArray.asList().stream().forEach(sink);
+ return list;
+ }
+ }
+
+ private static void convertIcon(JsonElement elem, Consumer> sink) {
+ String name = elem.getAsJsonObject().get("properties").getAsJsonObject().get("name").getAsString();
+ int code = elem.getAsJsonObject().get("properties").getAsJsonObject().get("code").getAsInt();
+ for(String n: name.split(",")) {
+ sink.accept(Pair.of(n.strip(), code));
+ }
+ }
+
+ private static String renderSassMixins(List> list) {
+ StringBuffer buffer = new StringBuffer();
+
+ list.forEach(icon -> {
+ buffer.append("@mixin icon-" + icon.getLeft() + "-content {content:\"\\" + Integer.toHexString(icon.getRight()) + "\";}\n");
+ });
+
+ return buffer.toString();
+ }
+
+ private static String renderSassIcons(List> list) {
+ StringBuffer buffer = new StringBuffer();
+
+ list.forEach(icon -> {
+ buffer.append(".icon-" + icon.getLeft() + ":before{@include icon-"+icon.getLeft()+"-content;}\n\n");
+ });
+
+ return buffer.toString();
+ }
+
+ private String renderMixinsTemplate() throws Exception {
+ try (FileReader reader = new FileReader(inputMixinsTemplateFilePath)) {
+ return IOUtils.toString(reader).replace("{{$HASH$}}", md5ToNumeric(getHash(inputFile))).replace("{{$ICONS$}}", renderSassMixins(getIcons()));
+ }
+ }
+
+ private String renderIconsTemplate() throws Exception {
+ try (FileReader reader = new FileReader(inputIconsTemplateFilePath)) {
+ return IOUtils.toString(reader).replace("{{$HASH$}}", md5ToNumeric(getHash(inputFile))).replace("{{$ICONS$}}", renderSassIcons(getIcons()));
+ }
+ }
+
+ private static String md5ToNumeric(String md5Hash) {
+ return new BigInteger(md5Hash, 16).toString();
+ }
+
+ private static String getHash(File file) throws Exception {
+ MessageDigest digest = MessageDigest.getInstance("MD5");
+ FileInputStream fis = new FileInputStream(file);
+ byte[] byteArray = new byte[1024];
+ int bytesCount = 0;
+
+ // Read the file data and update it to the message digest
+ while ((bytesCount = fis.read(byteArray)) != -1) {
+ digest.update(byteArray, 0, bytesCount);
+ };
+
+ fis.close();
+
+ // Get the hash's bytes
+ byte[] bytes = digest.digest();
+
+ // Convert it to hexadecimal format
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < bytes.length; i++) {
+ sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
+ }
+
+ // Return the complete hash
+ return sb.toString();
+ }
+
+// private void replaceAllIconsInFile(File file) throws Exception {
+// String filePath = file.getAbsolutePath();
+// try(Stream lines = Files.lines(Paths.get(filePath))){
+// String content = String.join("\n", (Iterable)lines::iterator);
+// for(Pair ico: getIcons()) {
+// String from = "content:\"\\" + Integer.toHexString(ico.getRight()) + "\";";
+// String to = "@include icon-" + ico.getLeft().strip() + "-content;";
+// content = content.replace(from, to);
+// }
+//
+// Files.write(Paths.get(filePath), content.getBytes(), StandardOpenOption.WRITE);
+// } catch (IOException e) {
+// e.printStackTrace();
+// }
+// }
+
+}
diff --git a/src/main/java/net/ftlines/css/scoper/maven/FindMissingIconMojo.java b/src/main/java/net/ftlines/css/scoper/maven/FindMissingIconMojo.java
new file mode 100644
index 0000000..6e2b15d
--- /dev/null
+++ b/src/main/java/net/ftlines/css/scoper/maven/FindMissingIconMojo.java
@@ -0,0 +1,117 @@
+package net.ftlines.css.scoper.maven;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.shared.model.fileset.FileSet;
+import org.apache.maven.shared.model.fileset.util.FileSetManager;
+import org.jsoup.Jsoup;
+import org.jsoup.select.Elements;
+
+
+@Mojo(name = "find-icon-violations", defaultPhase = LifecyclePhase.VERIFY)
+public class FindMissingIconMojo extends AbstractMojo {
+
+ @Parameter(required = true)
+ public File inputFile;
+
+ @Parameter(required = true, defaultValue = "")
+ public FileSet fileset;
+
+ @Parameter(required = true, defaultValue = "false")
+ public boolean failOnError = false;
+
+ private FileSetManager fileSetManager = new FileSetManager();
+
+ private List> getIcons() throws Exception {
+ return CompileIconSelection.getIcons(inputFile);
+ }
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ try {
+
+ List iconClasses = getIcons().stream().map(z-> "icon-" + z.getLeft()).collect(Collectors.toList());
+ findIconViolations(iconClasses, fileset);
+
+ List iconCharacterCodes = getIcons().stream().map(z-> ("\\" + Integer.toHexString(z.getRight()))).collect(Collectors.toList());
+ findIconContentViolations(iconCharacterCodes, fileset);
+
+ } catch(Exception ex) {
+ if(ex instanceof MojoExecutionException) {
+ throw (MojoExecutionException)ex;
+ }
+
+ if(ex instanceof MojoFailureException) {
+ throw (MojoFailureException)ex;
+ }
+
+ getLog().error(ex);
+ }
+ }
+
+ private void findIconContentViolations(List iconCharacterCodes, FileSet fileSet) throws MojoExecutionException {
+ for (String f : fileSetManager.getIncludedFiles(fileset)) {
+ try {
+ List lines = Files.readAllLines(Path.of(fileset.getDirectory(), f));
+
+ for (String line : lines) {
+ if (line.contains("content")) {
+ String contentCode = extractContentCode(line);
+ if (contentCode != null && !iconCharacterCodes.contains(contentCode)) {
+ getLog().warn("Found violation in " + f + ": " + line.strip());
+ if (failOnError) {
+ throw new MojoExecutionException("Icon violation found in " + f);
+ }
+ }
+ }
+ }
+ } catch (IOException e) {
+ getLog().error("Error reading file: " + f, e);
+ }
+ }
+ }
+
+ private String extractContentCode(String line) {
+ Pattern pattern = Pattern.compile("content:\\s*\"\\\\([0-9a-fA-F]+)\";");
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.find()) {
+ return "\\" + matcher.group(1);
+ }
+ return null;
+ }
+
+
+ private void findIconViolations(List iconClasses, FileSet fileSet) throws IOException, MojoFailureException {
+ for (String f : fileSetManager.getIncludedFiles(fileset)) {
+
+ org.jsoup.nodes.Document doc = Jsoup.parse(Files.readString(Path.of(fileset.getDirectory(), f)), "UTF-8");
+ Elements elementsWithIconClass = doc.select("[class^=icon-]");
+
+ for (org.jsoup.nodes.Element element : elementsWithIconClass) {
+ for(String className: element.className().split(" ")) {
+ if (className.startsWith("icon-") && !iconClasses.contains(className)) {
+ getLog().warn("Icon violation found in " + f + ": Class " + className + " is not in the allowed list.");
+ if (failOnError) {
+ throw new MojoFailureException("Icon violation found: Class " + className + " is not in the allowed list.");
+ }
+ }
+ }
+ }
+ }
+ }
+
+}