Fix resources on sub-paths, Simplify path handling, Add more date formats
This commit is contained in:
parent
a6be47a40f
commit
7bc87c39c3
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
58
src/main/java/me/mrletsplay/mdblog/util/TimeFormatter.java
Normal file
58
src/main/java/me/mrletsplay/mdblog/util/TimeFormatter.java
Normal 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");
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
### {title}
|
||||
#### by {author} on {date}
|
||||
#### by {author} {date_relative}
|
||||
*{tags}*
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user