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>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.11.0</version>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20250107</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<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;
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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");
|
||||
|
|
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;
|
||||
|
||||
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<String, Object>();
|
||||
|
||||
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<String, Object> 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;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -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