Skip to content

Commit

Permalink
Adding a sass icon generation task that produces icon files from an i…
Browse files Browse the repository at this point in the history
…comoon descriptor
  • Loading branch information
pfranza committed Jan 2, 2024
1 parent 98a216f commit 2e53d21
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>net.ftlines</groupId>
<artifactId>wicket-css-scope</artifactId>
<version>2.1.0-SNAPSHOT</version>
<version>2.1.1-SNAPSHOT</version>
<packaging>maven-plugin</packaging>

<name>42 Lines - Wicket Css Scoping</name>
Expand Down
166 changes: 166 additions & 0 deletions src/main/java/net/ftlines/css/scoper/maven/CompileIconSelection.java
Original file line number Diff line number Diff line change
@@ -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<Pair<String, Integer>> getIcons() throws Exception {
return getIcons(inputFile);
}

protected static List<Pair<String, Integer>> 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<Pair<String, Integer>> list = new ArrayList<Pair<String,Integer>>();

Consumer<JsonElement> sink = (z) -> {
convertIcon(z, list::add);
};

iconArray.asList().stream().forEach(sink);
return list;
}
}

private static void convertIcon(JsonElement elem, Consumer<Pair<String, Integer>> 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<Pair<String, Integer>> 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<Pair<String, Integer>> 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<String> lines = Files.lines(Paths.get(filePath))){
// String content = String.join("\n", (Iterable<String>)lines::iterator);
// for(Pair<String, Integer> 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();
// }
// }

}
117 changes: 117 additions & 0 deletions src/main/java/net/ftlines/css/scoper/maven/FindMissingIconMojo.java
Original file line number Diff line number Diff line change
@@ -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<Pair<String, Integer>> getIcons() throws Exception {
return CompileIconSelection.getIcons(inputFile);
}

@Override
public void execute() throws MojoExecutionException, MojoFailureException {
try {

List<String> iconClasses = getIcons().stream().map(z-> "icon-" + z.getLeft()).collect(Collectors.toList());
findIconViolations(iconClasses, fileset);

List<String> 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<String> iconCharacterCodes, FileSet fileSet) throws MojoExecutionException {
for (String f : fileSetManager.getIncludedFiles(fileset)) {
try {
List<String> 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<String> 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.");
}
}
}
}
}
}

}

0 comments on commit 2e53d21

Please sign in to comment.