Fix resources on sub-paths, Simplify path handling, Add more date formats

This commit is contained in:
MrLetsplay 2024-02-15 22:25:28 +01:00
parent a6be47a40f
commit 7bc87c39c3
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
5 changed files with 164 additions and 72 deletions

View File

@ -19,6 +19,7 @@ import me.mrletsplay.mdblog.blog.PostMetadata;
import me.mrletsplay.mdblog.markdown.MdParser;
import me.mrletsplay.mdblog.markdown.MdRenderer;
import me.mrletsplay.mdblog.util.PostPath;
import me.mrletsplay.mdblog.util.TimeFormatter;
import me.mrletsplay.simplehttpserver.dom.html.HtmlDocument;
import me.mrletsplay.simplehttpserver.dom.html.HtmlElement;
import me.mrletsplay.simplehttpserver.http.HttpRequestMethod;
@ -42,6 +43,7 @@ public class MdBlog {
private static List<WatchKey> watchedDirectories;
private static Map<PostPath, Post> posts;
private static Map<PostPath, String> indexTemplates;
private static Map<PostPath, FileDocument> globalResources;
private static String
defaultIndexTemplate,
@ -54,53 +56,8 @@ public class MdBlog {
.port(3706)
.create());
server.getDocumentProvider().register(HttpRequestMethod.GET, "/", () -> {
createPostsIndex(null);
});
server.getDocumentProvider().registerPattern(HttpRequestMethod.GET, "/{path...}", () -> {
HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
String rawPath = ctx.getPathParameters().get("path");
PostPath path = PostPath.parse(rawPath);
Post post = posts.get(path);
if(post != null) {
post.getContent().createContent();
return;
}
if(posts.keySet().stream().anyMatch(p -> p.startsWith(path))) {
if(!rawPath.endsWith("/")) {
ctx.redirect(path.subPath(path.length() - 1).toString() + "/");
return;
}
createPostsIndex(path);
return;
}
Path resolved = POSTS_PATH.resolve(path.toNioPath()).normalize();
if(!resolved.startsWith(POSTS_PATH)) {
server.getDocumentProvider().getNotFoundDocument().createContent();
return;
}
if(!Files.isRegularFile(resolved) || !Files.isReadable(resolved)) {
server.getDocumentProvider().getNotFoundDocument().createContent();
return;
}
try {
new FileDocument(resolved).createContent();
} catch (IOException e) {
e.printStackTrace();
ctx.setException(e);
server.getDocumentProvider().getErrorDocument().createContent();
}
});
extractAndRegister("style/base.css");
extractAndRegister("style/index.css");
extractAndRegister("style/post.css");
server.getDocumentProvider().register(HttpRequestMethod.GET, "/", () -> createPostsIndex(PostPath.root()));
server.getDocumentProvider().registerPattern(HttpRequestMethod.GET, "/{path...}", () -> handleRequest(HttpRequestContext.getCurrentContext()));
defaultIndexTemplate = Files.readString(extract("template/index.md"));
defaultIndexPostTemplate = Files.readString(extract("template/index-post.md"));
@ -116,6 +73,12 @@ public class MdBlog {
posts = new HashMap<>();
indexTemplates = new HashMap<>();
globalResources = new HashMap<>();
extractAndRegister("style/base.css");
extractAndRegister("style/index.css");
extractAndRegister("style/post.css");
updateBlogs();
watchFolders();
@ -154,26 +117,16 @@ public class MdBlog {
.filter(p -> p.length() == 1)
.toList();
String blogName = path == null ? "/" : path.getName();
String blogName = path.getName();
String indexTemplate;
String indexSubBlogTemplate;
String indexPostTemplate;
if(path != null) {
indexTemplate = indexTemplates.getOrDefault(path.concat(PostPath.parse(INDEX_NAME)), defaultIndexTemplate);
indexSubBlogTemplate = indexTemplates.getOrDefault(path.concat(PostPath.parse(INDEX_SUB_BLOG_NAME)), defaultIndexSubBlogTemplate);
indexPostTemplate = indexTemplates.getOrDefault(path.concat(PostPath.parse(INDEX_POST_NAME)), defaultIndexPostTemplate);
}else {
indexTemplate = defaultIndexTemplate;
indexSubBlogTemplate = defaultIndexSubBlogTemplate;
indexPostTemplate = defaultIndexPostTemplate;
}
String indexTemplate = indexTemplates.getOrDefault(path.concat(PostPath.parse(INDEX_NAME)), defaultIndexTemplate);
String indexSubBlogTemplate = indexTemplates.getOrDefault(path.concat(PostPath.parse(INDEX_SUB_BLOG_NAME)), defaultIndexSubBlogTemplate);
String indexPostTemplate = indexTemplates.getOrDefault(path.concat(PostPath.parse(INDEX_POST_NAME)), defaultIndexPostTemplate);
HtmlDocument index = new HtmlDocument();
index.setTitle("Index of " + blogName);
index.addStyleSheet("/_/style/base.css");
index.addStyleSheet("/_/style/index.css");
index.addStyleSheet("_/style/base.css");
index.addStyleSheet("_/style/index.css");
String indexMd = indexTemplate;
indexMd = indexMd.replace("{name}", blogName);
@ -201,7 +154,9 @@ public class MdBlog {
postMd = postMd.replace("{title}", title.toString());
postMd = postMd.replace("{author}", meta.author());
postMd = postMd.replace("{date}", meta.date().toString());
postMd = postMd.replace("{date}", TimeFormatter.toDateOnly(meta.date()));
postMd = postMd.replace("{date_time}", TimeFormatter.toDateAndTime(meta.date()));
postMd = postMd.replace("{date_relative}", TimeFormatter.toRelativeTime(meta.date()));
postMd = postMd.replace("{tags}", meta.tags().stream().collect(Collectors.joining(", ")));
postMd = postMd.replace("{description}", meta.description());
return postMd;
@ -212,6 +167,59 @@ public class MdBlog {
index.createContent();
}
private static void handleRequest(HttpRequestContext ctx) {
String rawPath = ctx.getPathParameters().get("path");
PostPath path = PostPath.parse(rawPath);
int index = 0;
while(index < path.length() - 1 && !path.getSegments()[index].equals("_")) index++;
if(index < path.length() - 1) {
PostPath resourcePath = path.subPath(index + 1);
FileDocument resource = globalResources.get(resourcePath);
if(resource != null) {
resource.createContent();
}else {
server.getDocumentProvider().getNotFoundDocument().createContent();
}
return;
}
Post post = posts.get(path);
if(post != null) {
post.getContent().createContent();
return;
}
if(posts.keySet().stream().anyMatch(p -> p.startsWith(path))) {
if(!rawPath.endsWith("/")) {
ctx.redirect(path.subPath(path.length() - 1).toString() + "/");
return;
}
createPostsIndex(path);
return;
}
Path resolved = POSTS_PATH.resolve(path.toNioPath()).normalize();
if(!resolved.startsWith(POSTS_PATH)) {
server.getDocumentProvider().getNotFoundDocument().createContent();
return;
}
if(!Files.isRegularFile(resolved) || !Files.isReadable(resolved)) {
server.getDocumentProvider().getNotFoundDocument().createContent();
return;
}
try {
new FileDocument(resolved).createContent();
} catch (IOException e) {
e.printStackTrace();
ctx.setException(e);
server.getDocumentProvider().getErrorDocument().createContent();
}
}
private static Path extract(String path) throws IOException {
Path filePath = FILES_PATH.resolve(path);
if(!Files.exists(filePath)) {
@ -222,7 +230,7 @@ public class MdBlog {
}
private static void extractAndRegister(String path) throws IOException {
server.getDocumentProvider().register(HttpRequestMethod.GET, "/_/" + path, new FileDocument(extract(path)));
globalResources.put(PostPath.parse(path), new FileDocument(extract(path)));
}
private static void updateBlogs() throws IOException {

View File

@ -4,6 +4,7 @@ import java.time.DateTimeException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
@ -34,7 +35,13 @@ public record PostMetadata(Instant date, String title, String author, Set<String
}
case "title" -> title = value;
case "author" -> author = value;
case "tags" -> tags = Arrays.stream(value.split(",")).map(String::trim).collect(Collectors.toUnmodifiableSet());
case "tags" -> {
Set<String> t = Arrays.stream(value.split(","))
.map(String::trim)
.collect(Collectors.toCollection(LinkedHashSet::new));
tags = Collections.unmodifiableSet(t);
}
}
}

View File

@ -4,14 +4,27 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
public record PostPath(String[] segments) {
public class PostPath {
public PostPath {
private static final PostPath ROOT = new PostPath();
private final String[] segments;
private PostPath(String[] segments) {
if(segments.length == 0) throw new IllegalArgumentException("Number of segments must be greater than 0");
this.segments = segments;
}
private PostPath() {
this.segments = new String[0];
}
public String[] getSegments() {
return segments;
}
public PostPath getParent() {
if(segments.length == 1) return null;
if(segments.length == 1) return ROOT;
return new PostPath(Arrays.copyOfRange(segments, 0, segments.length - 1));
}
@ -41,6 +54,7 @@ public record PostPath(String[] segments) {
}
public String getName() {
if(this == ROOT) return "/";
return segments[segments.length - 1];
}
@ -49,6 +63,7 @@ public record PostPath(String[] segments) {
}
public Path toNioPath() {
if(this == ROOT) return Paths.get("/");
return Paths.get(segments[0], Arrays.copyOfRange(segments, 1, segments.length));
}
@ -77,13 +92,17 @@ public record PostPath(String[] segments) {
return String.join("/", segments);
}
public static PostPath root() {
return ROOT;
}
public static PostPath parse(String path) {
if(path == null || path.isEmpty()) throw new IllegalArgumentException("Path must not be null or empty");
return new PostPath(path.split("/"));
}
public static PostPath of(Path path) throws IllegalArgumentException {
if(path.getNameCount() == 0) throw new IllegalArgumentException("Path must not be a root path");
public static PostPath of(Path path) {
if(path.getNameCount() == 0) return ROOT;
String[] names = new String[path.getNameCount()];
for(int i = 0; i < path.getNameCount(); i++) {
names[i] = path.getName(i).toString();

View File

@ -0,0 +1,58 @@
package me.mrletsplay.mdblog.util;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
public class TimeFormatter {
private static final DateTimeFormatter
DATE_ONLY = DateTimeFormatter.ISO_LOCAL_DATE,
DATE_AND_TIME = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.appendLiteral(" ")
.append(DateTimeFormatter.ISO_LOCAL_TIME)
.toFormatter();
public static String toDateOnly(Instant instant) {
return DATE_ONLY.format(instant.atZone(ZoneId.systemDefault()));
}
public static String toDateAndTime(Instant instant) {
return DATE_AND_TIME.format(instant.atZone(ZoneId.systemDefault()));
}
public static String toRelativeTime(Instant instant) {
// TODO: potentially use ChronoUnit#between instead to support more units
Period p = Period.between(LocalDate.now(), instant.atZone(ZoneId.systemDefault()).toLocalDate());
if(p.isZero()) return "today";
boolean negative = p.isNegative();
if(negative) p = p.negated();
StringBuilder b = new StringBuilder();
if(!negative) b.append("in ");
if(p.getYears() > 0) {
appendTime(b, p.getYears(), "year");
}else if(p.getMonths() > 0) {
appendTime(b, p.getMonths(), "month");
}else if(p.getDays() > 0) {
appendTime(b, p.getDays(), "day");
}
if(negative) b.append(" ago");
return b.toString();
}
private static void appendTime(StringBuilder builder, int x, String unit) {
builder.append(x).append(" ").append(unit);
if(x > 1) builder.append("s");
}
}

View File

@ -1,5 +1,5 @@
### {title}
#### by {author} on {date}
#### by {author} {date_relative}
*{tags}*