this
This commit is contained in:
parent
e6cd257238
commit
e3e07dc3fe
8 changed files with 273 additions and 84 deletions
6
pom.xml
6
pom.xml
|
@ -32,9 +32,9 @@
|
||||||
<version>3.2.2</version>
|
<version>3.2.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.code.gson</groupId>
|
<groupId>org.json</groupId>
|
||||||
<artifactId>gson</artifactId>
|
<artifactId>json</artifactId>
|
||||||
<version>2.11.0</version>
|
<version>20250107</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>commons-io</groupId>
|
||||||
|
|
88
src/main/java/eu/m724/blog/ImageFilter.java
Normal file
88
src/main/java/eu/m724/blog/ImageFilter.java
Normal file
|
@ -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<String, Object> 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<String> getArgumentNames() {
|
||||||
|
return List.of("blur");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
|
@ -1,8 +1,8 @@
|
||||||
package eu.m724.blog;
|
package eu.m724.blog;
|
||||||
|
|
||||||
|
import eu.m724.blog.data.Feed;
|
||||||
import eu.m724.blog.data.Post;
|
import eu.m724.blog.data.Post;
|
||||||
import eu.m724.blog.data.Site;
|
import eu.m724.blog.data.Site;
|
||||||
import eu.m724.blog.data.Template;
|
|
||||||
import in.wilsonl.minifyhtml.Configuration;
|
import in.wilsonl.minifyhtml.Configuration;
|
||||||
import in.wilsonl.minifyhtml.MinifyHtml;
|
import in.wilsonl.minifyhtml.MinifyHtml;
|
||||||
import org.apache.commons.io.file.PathUtils;
|
import org.apache.commons.io.file.PathUtils;
|
||||||
|
@ -27,11 +27,16 @@ public class Main {
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
System.out.println("Hello world!");
|
System.out.println("Hello world!");
|
||||||
|
|
||||||
|
var start = System.nanoTime();
|
||||||
|
|
||||||
var workingDirectory = Path.of("m724");
|
var workingDirectory = Path.of("m724");
|
||||||
var templateDirectory = workingDirectory.resolve("template");
|
var templateDirectory = workingDirectory.resolve("template");
|
||||||
var outputDirectory = workingDirectory.resolve("generated_out");
|
var outputDirectory = workingDirectory.resolve("generated_out");
|
||||||
var force = true;
|
var force = true;
|
||||||
|
|
||||||
|
var server = true;
|
||||||
|
var openBrowser = true;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
var repository = new RepositoryBuilder()
|
var repository = new RepositoryBuilder()
|
||||||
|
@ -50,7 +55,7 @@ public class Main {
|
||||||
}
|
}
|
||||||
|
|
||||||
var site = Site.fromConfig(git);
|
var site = Site.fromConfig(git);
|
||||||
var template = new Template(templateDirectory);
|
var template = new TemplateRenderer(outputDirectory, templateDirectory);
|
||||||
|
|
||||||
Files.createDirectory(outputDirectory);
|
Files.createDirectory(outputDirectory);
|
||||||
|
|
||||||
|
@ -88,7 +93,27 @@ public class Main {
|
||||||
posts.sort(Comparator.comparing(Post::createdAt).reversed());
|
posts.sort(Comparator.comparing(Post::createdAt).reversed());
|
||||||
Files.writeString(outputDirectory.resolve("index.html"), template.renderIndex(site, posts));
|
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 {
|
private static boolean copyTree(Path srcDir, Path destDir) throws IOException {
|
||||||
|
|
|
@ -11,23 +11,31 @@ import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public class Server implements HttpHandler {
|
public class Server implements HttpHandler {
|
||||||
private final InetSocketAddress listenAddress = new InetSocketAddress("localhost",8010);
|
private final InetSocketAddress listenAddress;
|
||||||
private final Path directory;
|
private final Path webroot;
|
||||||
|
|
||||||
public Server(Path directory) {
|
public Server(InetSocketAddress listenAddress, Path webroot) {
|
||||||
this.directory = directory;
|
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);
|
var server = HttpServer.create(listenAddress, 0);
|
||||||
server.createContext("/", this);
|
server.createContext("/", this);
|
||||||
server.start();
|
server.start();
|
||||||
System.out.println("Server started on " + listenAddress);
|
|
||||||
|
System.out.println("Server started on " + server.getAddress());
|
||||||
|
|
||||||
|
return server.getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(HttpExchange exchange) throws IOException {
|
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)) {
|
if (Files.isDirectory(path)) {
|
||||||
path = path.resolve("index.html");
|
path = path.resolve("index.html");
|
||||||
|
|
95
src/main/java/eu/m724/blog/TemplateRenderer.java
Normal file
95
src/main/java/eu/m724/blog/TemplateRenderer.java
Normal file
|
@ -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<String, Function> getFunctions() {
|
||||||
|
return Map.of(
|
||||||
|
"static", new Function() {
|
||||||
|
@Override
|
||||||
|
public List<String> getArgumentNames() {
|
||||||
|
return List.of("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object execute(Map<String, Object> args, PebbleTemplate self, EvaluationContext context, int lineNumber) {
|
||||||
|
return "/static/" + args.get("path"); // TODO for more advanced stuff
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"asset", new Function() {
|
||||||
|
@Override
|
||||||
|
public List<String> getArgumentNames() {
|
||||||
|
return List.of("path");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object execute(Map<String, Object> 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<Post> posts) throws IOException {
|
||||||
|
Map<String, Object> 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<String, Object> context = Map.of(
|
||||||
|
"site", site,
|
||||||
|
"article", post
|
||||||
|
);
|
||||||
|
|
||||||
|
var writer = new StringWriter();
|
||||||
|
articleTemplate.evaluate(writer, context);
|
||||||
|
|
||||||
|
return MinifyHtml.minify(writer.toString(), configuration);
|
||||||
|
}
|
||||||
|
}
|
20
src/main/java/eu/m724/blog/data/Feed.java
Normal file
20
src/main/java/eu/m724/blog/data/Feed.java
Normal file
|
@ -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<Post> posts) {
|
||||||
|
var content = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><rss version=\"2.0\">";
|
||||||
|
content += "<channel><title>%s</title><link>%s</link>".formatted(site.name(), site.baseUrl());
|
||||||
|
|
||||||
|
var formatter = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz");
|
||||||
|
for (var post : posts) {
|
||||||
|
content += "<item><title>%s</title><link>%s/post/%s.html</link><description>%s</description><pubDate>%s</pubDate></item>".formatted(post.title(), site.baseUrl(), post.slug(), post.summary(), post.createdAt().format(formatter));
|
||||||
|
}
|
||||||
|
|
||||||
|
content += "</channel></rss>";
|
||||||
|
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,12 @@
|
||||||
package eu.m724.blog.data;
|
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.eclipse.jgit.api.Git;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.AbstractMap;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public record Site(
|
public record Site(
|
||||||
String name,
|
String name,
|
||||||
|
@ -20,22 +16,24 @@ public record Site(
|
||||||
) {
|
) {
|
||||||
public static Site fromConfig(Git git) throws IOException {
|
public static Site fromConfig(Git git) throws IOException {
|
||||||
var content = Files.readString(git.getRepository().getDirectory().toPath().getParent().resolve("config.json"));
|
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 name = null;
|
||||||
String baseUrl = null;
|
String baseUrl = null;
|
||||||
var custom = new HashMap<String, Object>();
|
var custom = new HashMap<String, Object>();
|
||||||
|
|
||||||
for (var entry : json.entrySet()) {
|
for (var key : json.keySet()) {
|
||||||
switch (entry.getKey()) {
|
var value = json.get(key);
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
case "name":
|
case "name":
|
||||||
name = entry.getValue().getAsString();
|
name = (String) value;
|
||||||
break;
|
break;
|
||||||
case "baseUrl":
|
case "baseUrl":
|
||||||
baseUrl = entry.getValue().getAsString();
|
baseUrl = (String) value;
|
||||||
break;
|
break;
|
||||||
default:
|
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<String, Object> deep(JsonObject jsonObject) {
|
private static Map<String, Object> deep(JsonObject jsonObject) {
|
||||||
return jsonObject.entrySet().stream()
|
return jsonObject.entrySet().stream()
|
||||||
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), eToO(e.getValue())))
|
.map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), eToO(e.getValue())))
|
||||||
|
@ -56,9 +56,21 @@ public record Site(
|
||||||
} else if (element.isJsonObject()) {
|
} else if (element.isJsonObject()) {
|
||||||
return deep(element.getAsJsonObject());
|
return deep(element.getAsJsonObject());
|
||||||
} else if (element.isJsonPrimitive()) {
|
} 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
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return 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<Post> posts) throws IOException {
|
|
||||||
Map<String, Object> 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<String, Object> context = Map.of(
|
|
||||||
"site", site,
|
|
||||||
"article", post
|
|
||||||
);
|
|
||||||
|
|
||||||
var writer = new StringWriter();
|
|
||||||
articleTemplate.evaluate(writer, context);
|
|
||||||
|
|
||||||
return MinifyHtml.minify(writer.toString(), configuration);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue