Skip to content

Instantly share code, notes, and snippets.

@Leo40Git
Last active January 8, 2022 20:45
Show Gist options
  • Save Leo40Git/21063d32474dbd5bc699cf873440abf8 to your computer and use it in GitHub Desktop.
Save Leo40Git/21063d32474dbd5bc699cf873440abf8 to your computer and use it in GitHub Desktop.
Bad Theme Updater
// Uses CorellanStoma#1996's Canary update mappings
// https://docs.google.com/spreadsheets/d/1rLm7KJInDfLVbdnnjxfOaFjwdr51vr7hZdGc61-iyy0/edit (download as CSV)
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;
public final class BadThemeUpdater {
public static final class ThemeFiles {
private final Map<Path, String> pathToContents;
private final Set<Path> dirtyPaths;
public ThemeFiles() {
pathToContents = new HashMap<>();
dirtyPaths = new HashSet<>();
}
public void collect(Path dir) throws IOException {
try (var ds = Files.newDirectoryStream(dir)) {
for (var child : ds) {
if (Files.isDirectory(child, LinkOption.NOFOLLOW_LINKS)) {
collect(child);
} else {
String name = child.getFileName().toString();
if (name.endsWith(".scss") || name.endsWith(".css")) {
pathToContents.put(child, String.join("\n", Files.readAllLines(child, StandardCharsets.UTF_8)));
}
}
}
}
}
private final class FileEntry implements Map.Entry<Path, String> {
private final Map.Entry<Path, String> delegate;
private FileEntry(Map.Entry<Path, String> delegate) {
this.delegate = delegate;
}
@Override
public Path getKey() {
return delegate.getKey();
}
@Override
public String getValue() {
return delegate.getValue();
}
@Override
public String setValue(String value) {
var old = delegate.setValue(value);
if (!value.equals(old)) {
dirtyPaths.add(delegate.getKey());
}
return old;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FileEntry fileEntry = (FileEntry) o;
return delegate.equals(fileEntry.delegate);
}
@Override
public int hashCode() {
return Objects.hash(delegate);
}
}
public Iterable<Map.Entry<Path, String>> getFiles() {
return () -> new Iterator<>() {
private final Iterator<Map.Entry<Path, String>> delegate = pathToContents.entrySet().iterator();
@Override
public boolean hasNext() {
return delegate.hasNext();
}
@Override
public Map.Entry<Path, String> next() {
return new FileEntry(delegate.next());
}
};
}
public void flush() throws IOException {
for (var dirtyPath : dirtyPaths) {
var content = pathToContents.get(dirtyPath);
if (content == null) {
continue;
}
Files.writeString(dirtyPath, content, StandardCharsets.UTF_8);
}
dirtyPaths.clear();
}
}
private static final int CLASSES_CSV_HEADER_LINES = 5;
private static Map<Pattern, String> parseClassesCSV(BufferedReader reader) throws IOException {
// skip header lines
for (int i = 0; i < CLASSES_CSV_HEADER_LINES; i++) {
if (reader.readLine() == null) {
throw new IOException("CSV file was too short!");
}
}
var map = new HashMap<Pattern, String>();
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split(",");
if (parts.length < 3 || !parts[0].isEmpty() || parts[1].isEmpty() || parts[2].isEmpty()) {
continue;
}
parts[1] = addClassSpecifier(parts[1]);
parts[2] = addClassSpecifier(parts[2]);
map.put(Pattern.compile(parts[1], Pattern.LITERAL), parts[2]);
}
return map;
}
private static String addClassSpecifier(String str) {
if (!str.startsWith(".")) {
return "." + str;
}
return str;
}
private static void updateClassReferences(Map<Pattern, String> classMap, ThemeFiles files) {
for (var entry : files.getFiles()) {
var contents = entry.getValue();
for (var classEntry : classMap.entrySet()) {
System.out.println(entry.getKey() + ": " + classEntry.getKey() + " -> " + classEntry.getValue());
contents = classEntry.getKey().matcher(contents).replaceAll(classEntry.getValue());
}
entry.setValue(contents);
}
}
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("2 args expected: <classes csv> <theme directory>");
return;
}
Path classesCSV = Paths.get(args[0]).toAbsolutePath();
Path themeDir = Paths.get(args[1]).toAbsolutePath();
Map<Pattern, String> classMap;
try (var reader = Files.newBufferedReader(classesCSV)) {
classMap = parseClassesCSV(reader);
} catch (IOException e) {
System.err.println("Failed to parse classes CSV from " + classesCSV);
e.printStackTrace();
System.exit(1);
return;
}
ThemeFiles files = new ThemeFiles();
try {
files.collect(themeDir);
} catch (IOException e) {
System.err.println("Failed to collect theme files from " + themeDir);
e.printStackTrace();
System.exit(1);
return;
}
updateClassReferences(classMap, files);
try {
files.flush();
} catch (IOException e) {
System.err.println("Failed to flush theme files");
e.printStackTrace();
System.exit(1);
return;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment