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.MdParser;
|
||||||
import me.mrletsplay.mdblog.markdown.MdRenderer;
|
import me.mrletsplay.mdblog.markdown.MdRenderer;
|
||||||
import me.mrletsplay.mdblog.util.PostPath;
|
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.HtmlDocument;
|
||||||
import me.mrletsplay.simplehttpserver.dom.html.HtmlElement;
|
import me.mrletsplay.simplehttpserver.dom.html.HtmlElement;
|
||||||
import me.mrletsplay.simplehttpserver.http.HttpRequestMethod;
|
import me.mrletsplay.simplehttpserver.http.HttpRequestMethod;
|
||||||
@ -42,6 +43,7 @@ public class MdBlog {
|
|||||||
private static List<WatchKey> watchedDirectories;
|
private static List<WatchKey> watchedDirectories;
|
||||||
private static Map<PostPath, Post> posts;
|
private static Map<PostPath, Post> posts;
|
||||||
private static Map<PostPath, String> indexTemplates;
|
private static Map<PostPath, String> indexTemplates;
|
||||||
|
private static Map<PostPath, FileDocument> globalResources;
|
||||||
|
|
||||||
private static String
|
private static String
|
||||||
defaultIndexTemplate,
|
defaultIndexTemplate,
|
||||||
@ -54,53 +56,8 @@ public class MdBlog {
|
|||||||
.port(3706)
|
.port(3706)
|
||||||
.create());
|
.create());
|
||||||
|
|
||||||
server.getDocumentProvider().register(HttpRequestMethod.GET, "/", () -> {
|
server.getDocumentProvider().register(HttpRequestMethod.GET, "/", () -> createPostsIndex(PostPath.root()));
|
||||||
createPostsIndex(null);
|
server.getDocumentProvider().registerPattern(HttpRequestMethod.GET, "/{path...}", () -> handleRequest(HttpRequestContext.getCurrentContext()));
|
||||||
});
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
defaultIndexTemplate = Files.readString(extract("template/index.md"));
|
defaultIndexTemplate = Files.readString(extract("template/index.md"));
|
||||||
defaultIndexPostTemplate = Files.readString(extract("template/index-post.md"));
|
defaultIndexPostTemplate = Files.readString(extract("template/index-post.md"));
|
||||||
@ -116,6 +73,12 @@ public class MdBlog {
|
|||||||
|
|
||||||
posts = new HashMap<>();
|
posts = new HashMap<>();
|
||||||
indexTemplates = new HashMap<>();
|
indexTemplates = new HashMap<>();
|
||||||
|
globalResources = new HashMap<>();
|
||||||
|
|
||||||
|
extractAndRegister("style/base.css");
|
||||||
|
extractAndRegister("style/index.css");
|
||||||
|
extractAndRegister("style/post.css");
|
||||||
|
|
||||||
updateBlogs();
|
updateBlogs();
|
||||||
watchFolders();
|
watchFolders();
|
||||||
|
|
||||||
@ -154,26 +117,16 @@ public class MdBlog {
|
|||||||
.filter(p -> p.length() == 1)
|
.filter(p -> p.length() == 1)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
String blogName = path == null ? "/" : path.getName();
|
String blogName = path.getName();
|
||||||
|
|
||||||
String indexTemplate;
|
String indexTemplate = indexTemplates.getOrDefault(path.concat(PostPath.parse(INDEX_NAME)), defaultIndexTemplate);
|
||||||
String indexSubBlogTemplate;
|
String indexSubBlogTemplate = indexTemplates.getOrDefault(path.concat(PostPath.parse(INDEX_SUB_BLOG_NAME)), defaultIndexSubBlogTemplate);
|
||||||
String indexPostTemplate;
|
String indexPostTemplate = indexTemplates.getOrDefault(path.concat(PostPath.parse(INDEX_POST_NAME)), defaultIndexPostTemplate);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
HtmlDocument index = new HtmlDocument();
|
HtmlDocument index = new HtmlDocument();
|
||||||
index.setTitle("Index of " + blogName);
|
index.setTitle("Index of " + blogName);
|
||||||
index.addStyleSheet("/_/style/base.css");
|
index.addStyleSheet("_/style/base.css");
|
||||||
index.addStyleSheet("/_/style/index.css");
|
index.addStyleSheet("_/style/index.css");
|
||||||
|
|
||||||
String indexMd = indexTemplate;
|
String indexMd = indexTemplate;
|
||||||
indexMd = indexMd.replace("{name}", blogName);
|
indexMd = indexMd.replace("{name}", blogName);
|
||||||
@ -201,7 +154,9 @@ public class MdBlog {
|
|||||||
postMd = postMd.replace("{title}", title.toString());
|
postMd = postMd.replace("{title}", title.toString());
|
||||||
|
|
||||||
postMd = postMd.replace("{author}", meta.author());
|
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("{tags}", meta.tags().stream().collect(Collectors.joining(", ")));
|
||||||
postMd = postMd.replace("{description}", meta.description());
|
postMd = postMd.replace("{description}", meta.description());
|
||||||
return postMd;
|
return postMd;
|
||||||
@ -212,6 +167,59 @@ public class MdBlog {
|
|||||||
index.createContent();
|
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 {
|
private static Path extract(String path) throws IOException {
|
||||||
Path filePath = FILES_PATH.resolve(path);
|
Path filePath = FILES_PATH.resolve(path);
|
||||||
if(!Files.exists(filePath)) {
|
if(!Files.exists(filePath)) {
|
||||||
@ -222,7 +230,7 @@ public class MdBlog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void extractAndRegister(String path) throws IOException {
|
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 {
|
private static void updateBlogs() throws IOException {
|
||||||
|
@ -4,6 +4,7 @@ import java.time.DateTimeException;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
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 "title" -> title = value;
|
||||||
case "author" -> author = 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.nio.file.Paths;
|
||||||
import java.util.Arrays;
|
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");
|
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() {
|
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));
|
return new PostPath(Arrays.copyOfRange(segments, 0, segments.length - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +54,7 @@ public record PostPath(String[] segments) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
if(this == ROOT) return "/";
|
||||||
return segments[segments.length - 1];
|
return segments[segments.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,6 +63,7 @@ public record PostPath(String[] segments) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Path toNioPath() {
|
public Path toNioPath() {
|
||||||
|
if(this == ROOT) return Paths.get("/");
|
||||||
return Paths.get(segments[0], Arrays.copyOfRange(segments, 1, segments.length));
|
return Paths.get(segments[0], Arrays.copyOfRange(segments, 1, segments.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,13 +92,17 @@ public record PostPath(String[] segments) {
|
|||||||
return String.join("/", segments);
|
return String.join("/", segments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static PostPath root() {
|
||||||
|
return ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
public static PostPath parse(String path) {
|
public static PostPath parse(String path) {
|
||||||
if(path == null || path.isEmpty()) throw new IllegalArgumentException("Path must not be null or empty");
|
if(path == null || path.isEmpty()) throw new IllegalArgumentException("Path must not be null or empty");
|
||||||
return new PostPath(path.split("/"));
|
return new PostPath(path.split("/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PostPath of(Path path) throws IllegalArgumentException {
|
public static PostPath of(Path path) {
|
||||||
if(path.getNameCount() == 0) throw new IllegalArgumentException("Path must not be a root path");
|
if(path.getNameCount() == 0) return ROOT;
|
||||||
String[] names = new String[path.getNameCount()];
|
String[] names = new String[path.getNameCount()];
|
||||||
for(int i = 0; i < path.getNameCount(); i++) {
|
for(int i = 0; i < path.getNameCount(); i++) {
|
||||||
names[i] = path.getName(i).toString();
|
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}
|
### {title}
|
||||||
#### by {author} on {date}
|
#### by {author} {date_relative}
|
||||||
*{tags}*
|
*{tags}*
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user