diff --git a/pom.xml b/pom.xml index fe2ce88..ebbbea8 100644 --- a/pom.xml +++ b/pom.xml @@ -32,9 +32,9 @@ 3.2.2 - com.google.code.gson - gson - 2.11.0 + org.json + json + 20250107 commons-io diff --git a/src/main/java/eu/m724/blog/ImageFilter.java b/src/main/java/eu/m724/blog/ImageFilter.java new file mode 100644 index 0000000..cf5dfb9 --- /dev/null +++ b/src/main/java/eu/m724/blog/ImageFilter.java @@ -0,0 +1,88 @@ +/*package eu.m724.blog; + +import io.pebbletemplates.pebble.error.PebbleException; +import io.pebbletemplates.pebble.extension.Filter; +import io.pebbletemplates.pebble.template.EvaluationContext; +import io.pebbletemplates.pebble.template.PebbleTemplate; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.ConvolveOp; +import java.awt.image.Kernel; +import java.io.IOException; +import java.nio.Buffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class ImageFilter implements Filter { + private final Path directory; + + public ImageFilter(Path directory) { + this.directory = directory; + } + + @Override + public Object apply(Object input, Map args, PebbleTemplate self, EvaluationContext context, int lineNumber) throws PebbleException { + Path path = null; + System.out.println("filter"); + + if (input instanceof Path p) { + path = directory.resolve(p); + } else if (input instanceof String s) { + path = Path.of(directory.toString(), s); + } + + System.out.println(path); + + if (path == null || !Files.isRegularFile(path)) + return null; + + var filter = "fx-"; + var out = path.resolveSibling("f").resolve(filter + path.getFileName()); + + if (Files.isRegularFile(out)) { + return directory.relativize(out).toString(); + } + + System.out.println(path); + BufferedImage image; + try (var is = Files.newInputStream(path)) { + image = ImageIO.read(is); + } catch (IOException e) { + System.err.println("Error processing " + path + " with " + filter + ": " + e.getMessage()); + return null; + } + + var blur = ((Long) args.getOrDefault("blur", 0)).intValue(); + + if (blur > 0) { + int size = blur * 2 + 1; + float[] data = new float[size * size]; + + Arrays.fill(data, 1.0f / (size * size)); + + Kernel kernel = new Kernel(size, size, data); + ConvolveOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_ZERO_FILL, null); + image = op.filter(image, null); + } + + try (var os = Files.newOutputStream(out)) { + ImageIO.write(image, "webp", os); + } catch (IOException e) { + System.err.println("Error processing " + path + " with " + filter + ": " + e.getMessage()); + return null; + } + + return directory.relativize(out).toString(); + } + + @Override + public List getArgumentNames() { + return List.of("blur"); + } +} +*/ \ No newline at end of file diff --git a/src/main/java/eu/m724/blog/Main.java b/src/main/java/eu/m724/blog/Main.java index 8a450b6..09b5884 100644 --- a/src/main/java/eu/m724/blog/Main.java +++ b/src/main/java/eu/m724/blog/Main.java @@ -1,8 +1,8 @@ package eu.m724.blog; +import eu.m724.blog.data.Feed; import eu.m724.blog.data.Post; import eu.m724.blog.data.Site; -import eu.m724.blog.data.Template; import in.wilsonl.minifyhtml.Configuration; import in.wilsonl.minifyhtml.MinifyHtml; import org.apache.commons.io.file.PathUtils; @@ -27,11 +27,16 @@ public class Main { public static void main(String[] args) throws IOException { System.out.println("Hello world!"); + var start = System.nanoTime(); + var workingDirectory = Path.of("m724"); var templateDirectory = workingDirectory.resolve("template"); var outputDirectory = workingDirectory.resolve("generated_out"); var force = true; + var server = true; + var openBrowser = true; + // var repository = new RepositoryBuilder() @@ -50,7 +55,7 @@ public class Main { } var site = Site.fromConfig(git); - var template = new Template(templateDirectory); + var template = new TemplateRenderer(outputDirectory, templateDirectory); Files.createDirectory(outputDirectory); @@ -88,7 +93,27 @@ public class Main { posts.sort(Comparator.comparing(Post::createdAt).reversed()); Files.writeString(outputDirectory.resolve("index.html"), template.renderIndex(site, posts)); - new Server(outputDirectory).start(); + Files.writeString(outputDirectory.resolve("posts.rss"), Feed.generateRss(site, posts)); + + + var end = System.nanoTime(); + System.out.printf("Exported to %s (%.2f ms)\n", outputDirectory, (end - start) / 1000000.0); + + if (server) { + var listenAddress = new Server(outputDirectory).start(); + if (openBrowser) { + try { + var process = Runtime.getRuntime().exec(new String[] { "xdg-open", "http://" + listenAddress.getHostString() + ":" + listenAddress.getPort() }); + var code = process.waitFor(); + if (code != 0) { + throw new Exception("Exit code " + code); + } + System.out.println("Opened browser"); + } catch (Exception e) { + System.out.println("Failed to open browser: " + e); + } + } + } } private static boolean copyTree(Path srcDir, Path destDir) throws IOException { diff --git a/src/main/java/eu/m724/blog/Server.java b/src/main/java/eu/m724/blog/Server.java index 29bf223..27e6bce 100644 --- a/src/main/java/eu/m724/blog/Server.java +++ b/src/main/java/eu/m724/blog/Server.java @@ -11,23 +11,31 @@ import java.nio.file.Files; import java.nio.file.Path; public class Server implements HttpHandler { - private final InetSocketAddress listenAddress = new InetSocketAddress("localhost",8010); - private final Path directory; + private final InetSocketAddress listenAddress; + private final Path webroot; - public Server(Path directory) { - this.directory = directory; + public Server(InetSocketAddress listenAddress, Path webroot) { + this.listenAddress = listenAddress; + this.webroot = webroot; } - public void start() throws IOException { + public Server(Path webroot) { + this(new InetSocketAddress("localhost", 0), webroot); + } + + public InetSocketAddress start() throws IOException { var server = HttpServer.create(listenAddress, 0); server.createContext("/", this); server.start(); - System.out.println("Server started on " + listenAddress); + + System.out.println("Server started on " + server.getAddress()); + + return server.getAddress(); } @Override public void handle(HttpExchange exchange) throws IOException { - var path = directory.resolve(exchange.getRequestURI().getPath().substring(1)); + var path = webroot.resolve(exchange.getRequestURI().getPath().substring(1)); if (Files.isDirectory(path)) { path = path.resolve("index.html"); diff --git a/src/main/java/eu/m724/blog/TemplateRenderer.java b/src/main/java/eu/m724/blog/TemplateRenderer.java new file mode 100644 index 0000000..d0af295 --- /dev/null +++ b/src/main/java/eu/m724/blog/TemplateRenderer.java @@ -0,0 +1,95 @@ +package eu.m724.blog; + +import eu.m724.blog.data.Post; +import eu.m724.blog.data.Site; +import in.wilsonl.minifyhtml.Configuration; +import in.wilsonl.minifyhtml.MinifyHtml; +import io.pebbletemplates.pebble.PebbleEngine; +import io.pebbletemplates.pebble.extension.AbstractExtension; +import io.pebbletemplates.pebble.extension.Function; +import io.pebbletemplates.pebble.loader.FileLoader; +import io.pebbletemplates.pebble.template.EvaluationContext; +import io.pebbletemplates.pebble.template.PebbleTemplate; + +import java.io.IOException; +import java.io.StringWriter; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class TemplateRenderer { + private final Configuration configuration; + private final PebbleTemplate indexTemplate, articleTemplate; + + public TemplateRenderer(Path outputDirectory, Path templateDirectory) { + this.configuration = new Configuration.Builder() + .setMinifyCss(true) + .setMinifyJs(true) + .build(); + + var loader = new FileLoader(); + loader.setPrefix(templateDirectory.toString()); + loader.setSuffix(".html"); + + var pebbleEngine = new PebbleEngine.Builder() + .loader(loader) + .extension(new AbstractExtension() { + @Override + public Map getFunctions() { + return Map.of( + "static", new Function() { + @Override + public List getArgumentNames() { + return List.of("path"); + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, int lineNumber) { + return "/static/" + args.get("path"); // TODO for more advanced stuff + } + }, + "asset", new Function() { + @Override + public List getArgumentNames() { + return List.of("path"); + } + + @Override + public Object execute(Map args, PebbleTemplate self, EvaluationContext context, int lineNumber) { + return "/assets/" + args.get("path"); // TODO for more advanced stuff + } + } + ); + } + }) + .build(); + + this.indexTemplate = pebbleEngine.getTemplate("index_template"); + this.articleTemplate = pebbleEngine.getTemplate("article_template"); + } + + public String renderIndex(Site site, ArrayList posts) throws IOException { + Map context = Map.of( + "site", site, + "articles", posts + ); + + var writer = new StringWriter(); + indexTemplate.evaluate(writer, context); + + return MinifyHtml.minify(writer.toString(), configuration); + } + + public String renderPost(Site site, Post post) throws IOException { + Map context = Map.of( + "site", site, + "article", post + ); + + var writer = new StringWriter(); + articleTemplate.evaluate(writer, context); + + return MinifyHtml.minify(writer.toString(), configuration); + } +} diff --git a/src/main/java/eu/m724/blog/data/Feed.java b/src/main/java/eu/m724/blog/data/Feed.java new file mode 100644 index 0000000..82c9b03 --- /dev/null +++ b/src/main/java/eu/m724/blog/data/Feed.java @@ -0,0 +1,20 @@ +package eu.m724.blog.data; + +import java.time.format.DateTimeFormatter; +import java.util.List; + +public class Feed { + public static String generateRss(Site site, List posts) { + var content = ""; + content += "%s%s".formatted(site.name(), site.baseUrl()); + + var formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz"); + for (var post : posts) { + content += "%s%s/post/%s.html%s%s".formatted(post.title(), site.baseUrl(), post.slug(), post.summary(), post.createdAt().format(formatter)); + } + + content += ""; + + return content; + } +} diff --git a/src/main/java/eu/m724/blog/data/Site.java b/src/main/java/eu/m724/blog/data/Site.java index 06e2508..68721a6 100644 --- a/src/main/java/eu/m724/blog/data/Site.java +++ b/src/main/java/eu/m724/blog/data/Site.java @@ -1,16 +1,12 @@ package eu.m724.blog.data; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import org.eclipse.jgit.api.Git; +import org.json.JSONObject; import java.io.IOException; import java.nio.file.Files; -import java.util.AbstractMap; import java.util.HashMap; import java.util.Map; -import java.util.stream.Collectors; public record Site( String name, @@ -20,22 +16,24 @@ public record Site( ) { public static Site fromConfig(Git git) throws IOException { var content = Files.readString(git.getRepository().getDirectory().toPath().getParent().resolve("config.json")); - var json = JsonParser.parseString(content).getAsJsonObject().asMap(); + var json = new JSONObject(content); String name = null; String baseUrl = null; var custom = new HashMap(); - for (var entry : json.entrySet()) { - switch (entry.getKey()) { + for (var key : json.keySet()) { + var value = json.get(key); + + switch (key) { case "name": - name = entry.getValue().getAsString(); + name = (String) value; break; case "baseUrl": - baseUrl = entry.getValue().getAsString(); + baseUrl = (String) value; break; default: - custom.put(entry.getKey(), eToO(entry.getValue())); + custom.put(key, value); } } @@ -44,6 +42,8 @@ public record Site( ); } + /* remained from gson + private static Map deep(JsonObject jsonObject) { return jsonObject.entrySet().stream() .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), eToO(e.getValue()))) @@ -56,9 +56,21 @@ public record Site( } else if (element.isJsonObject()) { return deep(element.getAsJsonObject()); } else if (element.isJsonPrimitive()) { + try { + return element.getAsBoolean(); + } catch (IllegalStateException e) { } + + try { + return element.getAsLong(); + } catch (NumberFormatException e) { } + + try { + return element.getAsDouble(); + } catch (NumberFormatException e) { } + // TODO } return null; - } + }*/ } diff --git a/src/main/java/eu/m724/blog/data/Template.java b/src/main/java/eu/m724/blog/data/Template.java deleted file mode 100644 index 06fef1d..0000000 --- a/src/main/java/eu/m724/blog/data/Template.java +++ /dev/null @@ -1,59 +0,0 @@ -package eu.m724.blog.data; - -import in.wilsonl.minifyhtml.Configuration; -import in.wilsonl.minifyhtml.MinifyHtml; -import io.pebbletemplates.pebble.PebbleEngine; -import io.pebbletemplates.pebble.loader.FileLoader; -import io.pebbletemplates.pebble.template.PebbleTemplate; - -import java.io.IOException; -import java.io.StringWriter; -import java.nio.file.Path; -import java.util.*; - -public class Template { - private final Configuration configuration; - private final PebbleTemplate indexTemplate, articleTemplate; - - public Template(Path directory) { - this.configuration = new Configuration.Builder() - .setMinifyCss(true) - .setMinifyJs(true) - .build(); - - var loader = new FileLoader(); - loader.setPrefix(directory.toString()); - loader.setSuffix(".html"); - - var pebbleEngine = new PebbleEngine.Builder() - .loader(loader) - .build(); - - this.indexTemplate = pebbleEngine.getTemplate("index_template"); - this.articleTemplate = pebbleEngine.getTemplate("article_template"); - } - - public String renderIndex(Site site, ArrayList posts) throws IOException { - Map context = Map.of( - "site", site, - "articles", posts - ); - - var writer = new StringWriter(); - indexTemplate.evaluate(writer, context); - - return MinifyHtml.minify(writer.toString(), configuration); - } - - public String renderPost(Site site, Post post) throws IOException { - Map context = Map.of( - "site", site, - "article", post - ); - - var writer = new StringWriter(); - articleTemplate.evaluate(writer, context); - - return MinifyHtml.minify(writer.toString(), configuration); - } -}