Refactoring, more abstraction
Signed-off-by: Minecon724 <git@m724.eu>
This commit is contained in:
parent
d6c33b4992
commit
62eb9903c0
15 changed files with 306 additions and 119 deletions
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
|
@ -2,6 +2,7 @@
|
|||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/example_workdir" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/m724" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -6,17 +6,19 @@
|
|||
|
||||
package eu.m724.blog;
|
||||
|
||||
import eu.m724.blog.compress.CommonsCompressor;
|
||||
import eu.m724.blog.compress.CompressException;
|
||||
import eu.m724.blog.compress.FileCompressor;
|
||||
import eu.m724.blog.compress.NoSuchAlgorithmException;
|
||||
import eu.m724.blog.object.Article;
|
||||
import eu.m724.blog.object.Feed;
|
||||
import eu.m724.blog.object.RenderOptions;
|
||||
import eu.m724.blog.object.Site;
|
||||
import eu.m724.blog.server.Server;
|
||||
import eu.m724.blog.template.TemplateRenderer;
|
||||
import org.apache.commons.compress.compressors.CompressorException;
|
||||
import eu.m724.blog.vc.GitVersionControl;
|
||||
import eu.m724.blog.vc.VersionControl;
|
||||
import org.apache.commons.io.file.PathUtils;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.lib.RepositoryBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -29,18 +31,18 @@ import java.util.stream.Collectors;
|
|||
|
||||
/**
|
||||
* The {@code BlogBuilder} class facilitates building a static blog by managing templates,
|
||||
* assets, articles, and rendering output files. It uses a Git repository as the
|
||||
* assets, articles, and rendering output files. It uses a version control (Git etc.) repository as the
|
||||
* source for the blog's content and configuration.
|
||||
*/
|
||||
public class BlogBuilder {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(BlogBuilder.class);
|
||||
|
||||
private final Git git;
|
||||
private final Site site;
|
||||
private final RenderOptions renderOptions;
|
||||
private final VersionControl versionControl;
|
||||
private final Path workingDirectory;
|
||||
|
||||
private Site site;
|
||||
private TemplateRenderer template;
|
||||
private RenderOptions renderOptions;
|
||||
private Minifier minifier;
|
||||
|
||||
private Path templateDirectory;
|
||||
|
@ -50,12 +52,14 @@ public class BlogBuilder {
|
|||
/**
|
||||
* Constructs a {@link BlogBuilder} instance using the provided Git repository.
|
||||
*
|
||||
* @param git the Git repository to be used for the blog.
|
||||
* @param versionControl the version control repository to be used for the blog.
|
||||
*/
|
||||
public BlogBuilder(Git git) {
|
||||
this.git = git;
|
||||
private BlogBuilder(Site site, RenderOptions renderOptions, Path workingDirectory, VersionControl versionControl) {
|
||||
this.site = site;
|
||||
this.renderOptions = renderOptions;
|
||||
this.versionControl = versionControl;
|
||||
|
||||
this.workingDirectory = git.getRepository().getDirectory().toPath().getParent();
|
||||
this.workingDirectory = workingDirectory;
|
||||
this.templateDirectory = workingDirectory.resolve("template");
|
||||
this.outputDirectory = workingDirectory.resolve("generated_out");
|
||||
}
|
||||
|
@ -64,19 +68,19 @@ public class BlogBuilder {
|
|||
* Creates a new {@link BlogBuilder} instance for the specified working directory.
|
||||
* The directory is expected to be a Git repository.
|
||||
*
|
||||
* @param workingDirectory the root path of the blog, which must contain a Git repository.
|
||||
* @param directory the root path of the blog, which must be a Git repository.
|
||||
* @return a {@link BlogBuilder} instance
|
||||
* @throws IOException if there is an error accessing the Git repository
|
||||
*/
|
||||
public static BlogBuilder fromPath(Path workingDirectory) throws IOException {
|
||||
var repository = new RepositoryBuilder()
|
||||
.setGitDir(workingDirectory.resolve(".git").toFile())
|
||||
.build();
|
||||
var git = new Git(repository);
|
||||
public static BlogBuilder fromGitRepository(Path directory) throws IOException {
|
||||
return BlogBuilder.fromDirectory(directory, new GitVersionControl(directory));
|
||||
}
|
||||
|
||||
//
|
||||
public static BlogBuilder fromDirectory(Path directory, VersionControl versionControl) throws IOException {
|
||||
var site = Site.fromConfig(directory.resolve("site.yml"));
|
||||
var renderOptions = RenderOptions.fromConfig(directory.resolve("render.yml"));
|
||||
|
||||
return new BlogBuilder(git);
|
||||
return new BlogBuilder(site, renderOptions, directory, versionControl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,14 +134,7 @@ public class BlogBuilder {
|
|||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public void build() throws IOException {
|
||||
LOGGER.debug("Loading site...");
|
||||
if (site == null)
|
||||
this.site = Site.fromConfig(workingDirectory.resolve("site.yml"));
|
||||
|
||||
if (renderOptions == null)
|
||||
this.renderOptions = RenderOptions.fromConfig(workingDirectory.resolve("render.yml"));
|
||||
|
||||
if (this.site.minify()) {
|
||||
if (renderOptions.minify()) {
|
||||
try {
|
||||
this.minifier = new Minifier();
|
||||
} catch (NoClassDefFoundError e) {
|
||||
|
@ -193,8 +190,7 @@ public class BlogBuilder {
|
|||
continue;
|
||||
}
|
||||
|
||||
path = articleDirectory.relativize(path);
|
||||
var article = Article.fromFile(git, path);
|
||||
var article = Article.fromFile(versionControl, path);
|
||||
|
||||
if (article.draft() && !renderDrafts) {
|
||||
LOGGER.info("[Article {}] Draft. Ignoring, because you didn't specify to render drafts.", path.getFileName());
|
||||
|
@ -202,7 +198,7 @@ public class BlogBuilder {
|
|||
}
|
||||
|
||||
var render = template.renderArticle(article);
|
||||
var outFile = outputDirectory.resolve("article").resolve(path);
|
||||
var outFile = outputDirectory.resolve("article").resolve(path.getFileName());
|
||||
|
||||
try {
|
||||
Files.createDirectory(outFile.getParent());
|
||||
|
@ -229,8 +225,9 @@ public class BlogBuilder {
|
|||
if (renderOptions.remapAssets()) {
|
||||
var assetHashes = CacheBuster.copyTree(userAssetsDir, outputUserAssetsDir);
|
||||
|
||||
// This looks weird, but it's necessary if we want to use a single map
|
||||
assetHashes.forEach((k, v) -> {
|
||||
fileHashes.put("assets/" + k, v); // TODO this feels like a hack
|
||||
fileHashes.put("assets/" + k, v);
|
||||
});
|
||||
} else {
|
||||
FileUtils.copyTree(userAssetsDir, outputUserAssetsDir);
|
||||
|
@ -239,8 +236,9 @@ public class BlogBuilder {
|
|||
if (renderOptions.remapTemplateStatic()) {
|
||||
var templateStaticHashes = CacheBuster.copyTree(templateStaticDir, outputTemplateStaticDir);
|
||||
|
||||
// This looks weird, but it's necessary if we want to use a single map
|
||||
templateStaticHashes.forEach((k, v) -> {
|
||||
fileHashes.put("static/" + k, v); // TODO this feels like a hack
|
||||
fileHashes.put("static/" + k, v);
|
||||
});
|
||||
} else {
|
||||
FileUtils.copyTree(templateStaticDir, outputTemplateStaticDir);
|
||||
|
@ -259,7 +257,7 @@ public class BlogBuilder {
|
|||
|
||||
for (var algorithm : renderOptions.compress()) {
|
||||
try {
|
||||
var compressor = new FileCompressor(algorithm);
|
||||
var compressor = new CommonsCompressor(algorithm);
|
||||
compressors.add(compressor);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOGGER.error("No such compression algorithm, ignoring: {}", e.getAlgorithm());
|
||||
|
@ -276,8 +274,8 @@ public class BlogBuilder {
|
|||
for (var path : tree) {
|
||||
try {
|
||||
compressor.compress(path);
|
||||
} catch (CompressorException e) {
|
||||
LOGGER.error("Error compressing \"{}\" to \"{}\": {}", path, compressor.getAlgorithm(), e.getMessage());
|
||||
} catch (CompressException e) {
|
||||
LOGGER.error("Error compressing \"{}\" to \"{}\": {}", path, compressor.getAlgorithmName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,10 @@ public class Main {
|
|||
|
||||
var start = System.nanoTime();
|
||||
|
||||
var builder = BlogBuilder.fromPath(workingDirectory)
|
||||
|
||||
LOGGER.debug("Loading site...");
|
||||
|
||||
var builder = BlogBuilder.fromGitRepository(workingDirectory)
|
||||
.templateDirectory(templateDirectory)
|
||||
.outputDirectory(outputDirectory)
|
||||
.renderDrafts(renderDrafts);
|
||||
|
|
25
src/main/java/eu/m724/blog/YamlLoader.java
Normal file
25
src/main/java/eu/m724/blog/YamlLoader.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2025 blog-software-java developers
|
||||
* blog-software-java is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.blog;
|
||||
|
||||
import org.snakeyaml.engine.v2.api.Load;
|
||||
import org.snakeyaml.engine.v2.api.LoadSettings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
public class YamlLoader {
|
||||
private static final Load LOAD = new Load(LoadSettings.builder().build());
|
||||
|
||||
public static Map<String, Object> loadMap(Path file) throws IOException {
|
||||
try (var inputStream = Files.newInputStream(file)) {
|
||||
return (Map<String, Object>) LOAD.loadFromInputStream(inputStream);
|
||||
}
|
||||
}
|
||||
}
|
58
src/main/java/eu/m724/blog/compress/CommonsCompressor.java
Normal file
58
src/main/java/eu/m724/blog/compress/CommonsCompressor.java
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2025 blog-software-java developers
|
||||
* blog-software-java is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.blog.compress;
|
||||
|
||||
import org.apache.commons.compress.compressors.CompressorException;
|
||||
import org.apache.commons.compress.compressors.CompressorStreamFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class CommonsCompressor extends FileCompressor {
|
||||
/**
|
||||
* Constructs a {@link FileCompressor} instance using the provided algorithm.
|
||||
*
|
||||
* @param algorithm The algorithm, for valid names see {@link CompressorStreamFactory}
|
||||
* @throws NoSuchAlgorithmException If that algorithm is unavailable or the name is invalid.
|
||||
*/
|
||||
public CommonsCompressor(String algorithm) throws NoSuchAlgorithmException {
|
||||
super(algorithm);
|
||||
|
||||
try {
|
||||
var os = new CompressorStreamFactory().createCompressorOutputStream(algorithm, OutputStream.nullOutputStream());
|
||||
os.close();
|
||||
} catch (NoClassDefFoundError | CompressorException e) {
|
||||
throw new NoSuchAlgorithmException(algorithm, e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unexpected IOException closing test output stream", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compress(Path file) throws IOException, CompressException {
|
||||
var destination = file.resolveSibling(file.getFileName() + "." + getAlgorithmName());
|
||||
compress(file, destination);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compress(Path source, Path destination) throws IOException, CompressException {
|
||||
if (Files.exists(destination))
|
||||
throw new FileAlreadyExistsException(destination.toString());
|
||||
|
||||
try (
|
||||
var outputStream = new CompressorStreamFactory()
|
||||
.createCompressorOutputStream(getAlgorithmName(), Files.newOutputStream(destination))
|
||||
) {
|
||||
Files.copy(source, outputStream);
|
||||
} catch (CompressorException e) {
|
||||
throw new CompressException(e);
|
||||
}
|
||||
}
|
||||
}
|
14
src/main/java/eu/m724/blog/compress/CompressException.java
Normal file
14
src/main/java/eu/m724/blog/compress/CompressException.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright (c) 2025 blog-software-java developers
|
||||
* blog-software-java is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.blog.compress;
|
||||
|
||||
// TODO really runtime exception?
|
||||
public class CompressException extends RuntimeException {
|
||||
public CompressException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
|
@ -6,53 +6,20 @@
|
|||
|
||||
package eu.m724.blog.compress;
|
||||
|
||||
import org.apache.commons.compress.compressors.CompressorException;
|
||||
import org.apache.commons.compress.compressors.CompressorStreamFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.*;
|
||||
|
||||
public class FileCompressor {
|
||||
private final String algorithm;
|
||||
public abstract class FileCompressor {
|
||||
private final String algorithmName;
|
||||
|
||||
/**
|
||||
* Constructs a {@link FileCompressor} instance using the provided algorithm.
|
||||
*
|
||||
* @param algorithm The algorithm, for valid names see {@link CompressorStreamFactory}
|
||||
* @throws NoSuchAlgorithmException If that algorithm is unavailable or the name is invalid.
|
||||
*/
|
||||
public FileCompressor(String algorithm) throws NoSuchAlgorithmException {
|
||||
this.algorithm = algorithm;
|
||||
|
||||
try {
|
||||
var os = new CompressorStreamFactory().createCompressorOutputStream(algorithm, OutputStream.nullOutputStream());
|
||||
os.close();
|
||||
} catch (NoClassDefFoundError | CompressorException e) {
|
||||
throw new NoSuchAlgorithmException(algorithm, e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unexpected IOException closing test output stream", e);
|
||||
}
|
||||
protected FileCompressor(String algorithmName) {
|
||||
this.algorithmName = algorithmName;
|
||||
}
|
||||
|
||||
public void compress(Path source) throws IOException, CompressorException {
|
||||
var destination = source.resolveSibling(source.getFileName() + "." + algorithm);
|
||||
compress(source, destination);
|
||||
public String getAlgorithmName() {
|
||||
return algorithmName;
|
||||
}
|
||||
|
||||
public void compress(Path source, Path destination) throws IOException, CompressorException {
|
||||
if (Files.exists(destination))
|
||||
throw new FileAlreadyExistsException(destination.toString());
|
||||
|
||||
try (
|
||||
var outputStream = new CompressorStreamFactory()
|
||||
.createCompressorOutputStream(algorithm, Files.newOutputStream(destination))
|
||||
) {
|
||||
Files.copy(source, outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
abstract public void compress(Path file) throws IOException, CompressException;
|
||||
abstract public void compress(Path source, Path destination) throws IOException, CompressException;
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
package eu.m724.blog.object;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import eu.m724.blog.vc.VersionControl;
|
||||
import eu.m724.blog.vc.VersionControlException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -56,17 +56,16 @@ public record Article(
|
|||
* The method extracts metadata properties, content, and versioning information
|
||||
* based on the Git history of the file.
|
||||
*
|
||||
* @param git the Git repository used to retrieve versioning and commit information
|
||||
* @param versionControl the version control repository used to retrieve versioning and commit information
|
||||
* @param path the relative path to the file within the "articles" directory
|
||||
* @return a {@link Article} object populated with data extracted from the specified file
|
||||
* @throws IOException if an error occurs during file reading
|
||||
*/
|
||||
public static Article fromFile(Git git, Path path) throws IOException {
|
||||
public static Article fromFile(VersionControl versionControl, Path path) throws IOException {
|
||||
/* read properties before filtering */
|
||||
|
||||
var slug = path.getFileName().toString().split("\\.")[0];
|
||||
path = Path.of("articles").resolve(path);
|
||||
var lines = Files.readAllLines(git.getRepository().getDirectory().toPath().getParent().resolve(path));
|
||||
var lines = Files.readAllLines(path);
|
||||
|
||||
var properties = new HashMap<String, String>();
|
||||
|
||||
|
@ -121,18 +120,18 @@ public record Article(
|
|||
ZonedDateTime modifiedAt = Instant.ofEpochMilli(0).atZone(ZoneOffset.UTC);
|
||||
|
||||
try {
|
||||
for (var commit : git.log().addPath(path.toString()).call()) {
|
||||
createdBy = commit.getAuthorIdent().getName();
|
||||
createdAt = Instant.ofEpochSecond(commit.getCommitTime()).atZone(ZoneOffset.UTC);
|
||||
for (var change : versionControl.getChanges(path)) {
|
||||
createdBy = change.author();
|
||||
createdAt = change.time();
|
||||
|
||||
if (revisions++ == 0) {
|
||||
modifiedBy = createdBy;
|
||||
modifiedAt = createdAt;
|
||||
}
|
||||
}
|
||||
} catch (GitAPIException e) {
|
||||
} catch (VersionControlException e) {
|
||||
draft = true;
|
||||
LOGGER.warn("[Article {}] Draft because of a Git exception: {}", slug, e.getMessage());
|
||||
LOGGER.warn("[Article {}] Draft because of a VC exception: {}", slug, e.getMessage());
|
||||
}
|
||||
|
||||
return new Article(slug, title, summary, draft, revisions, createdBy, createdAt, modifiedBy, modifiedAt, custom, content);
|
||||
|
|
|
@ -6,41 +6,61 @@
|
|||
|
||||
package eu.m724.blog.object;
|
||||
|
||||
import org.snakeyaml.engine.v2.api.Load;
|
||||
import org.snakeyaml.engine.v2.api.LoadSettings;
|
||||
import eu.m724.blog.YamlLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Options that are relevant to rendering, not about the {@link Site}.
|
||||
*
|
||||
* @param compress list of compression algorithms to compress output
|
||||
* @param remapAssets whether to remap user assets, add a unique hash to the file name to bypass cache
|
||||
* @param remapTemplateStatic whether to remap template static assets, add a unique hash to the file name to bypass cache
|
||||
* @param minify whether to minify output
|
||||
*/
|
||||
public record RenderOptions(
|
||||
List<String> compress, // TODO rename?
|
||||
|
||||
boolean remapAssets,
|
||||
boolean remapTemplateStatic,
|
||||
boolean remapAssets
|
||||
|
||||
boolean minify
|
||||
) {
|
||||
private static final RenderOptions DEFAULT = new RenderOptions(
|
||||
List.of("gz", "zstd"),
|
||||
false,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a {@link Site} object by reading and parsing the configuration file at the specified path.<br>
|
||||
* Creates a {@link Site} object by reading and parsing the configuration file at the specified file.<br>
|
||||
* The configuration file must be a JSON file.
|
||||
*
|
||||
* @param path the path to the configuration file
|
||||
* @param file the file to the configuration file
|
||||
* @return a {@link Site} object initialized with the data from the configuration file
|
||||
* @throws IOException if an error occurs during file reading
|
||||
*/
|
||||
public static RenderOptions fromConfig(Path path) throws IOException {
|
||||
var load = new Load(LoadSettings.builder().build());
|
||||
var yaml = (Map<String, Object>) load.loadFromInputStream(Files.newInputStream(path));
|
||||
public static RenderOptions fromConfig(Path file) throws IOException {
|
||||
var yaml = YamlLoader.loadMap(file);
|
||||
|
||||
/* ---- */
|
||||
|
||||
List<String> compress = (List<String>) yaml.getOrDefault("compress", DEFAULT.compress());
|
||||
|
||||
List<String> compress = (List<String>) yaml.getOrDefault("compress", new ArrayList<>());
|
||||
boolean remapTemplateStatic = (boolean) yaml.getOrDefault("remapTemplateStatic", true);
|
||||
// assets are not remapped by default, because they might be hotlinked
|
||||
boolean remapAssets = (boolean) yaml.getOrDefault("remapAssets", false);
|
||||
boolean remapAssets = (boolean) yaml.getOrDefault("remapAssets", DEFAULT.remapAssets());
|
||||
|
||||
boolean remapTemplateStatic = (boolean) yaml.getOrDefault("remapTemplateStatic", DEFAULT.remapTemplateStatic());
|
||||
|
||||
var minify = (boolean) yaml.getOrDefault("minify", DEFAULT.minify());
|
||||
|
||||
/* ---- */
|
||||
|
||||
return new RenderOptions(
|
||||
compress, remapTemplateStatic, remapAssets
|
||||
compress, remapTemplateStatic, remapAssets, minify
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,9 @@
|
|||
|
||||
package eu.m724.blog.object;
|
||||
|
||||
import org.snakeyaml.engine.v2.api.Load;
|
||||
import org.snakeyaml.engine.v2.api.LoadSettings;
|
||||
import eu.m724.blog.YamlLoader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -21,7 +19,6 @@ import java.util.Map;
|
|||
* @param baseUrl the base URL of the site
|
||||
* @param directory The directory that the site is installed into. Like "https://example.com/blog" turns to "/blog"
|
||||
* @param templateArticles whether to parse articles with Pebble templating
|
||||
* @param minify whether to minify HTML, CSS, JS, etc.
|
||||
* @param custom a map of additional custom properties
|
||||
*/
|
||||
public record Site(
|
||||
|
@ -31,28 +28,33 @@ public record Site(
|
|||
String directory,
|
||||
|
||||
boolean templateArticles,
|
||||
boolean minify,
|
||||
|
||||
Map<String, Object> custom
|
||||
) {
|
||||
private static final Site DEFAULT = new Site(
|
||||
"Misconfigured blog",
|
||||
"/",
|
||||
"/",
|
||||
false,
|
||||
Map.of()
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates a {@link Site} object by reading and parsing the configuration file at the specified path.<br>
|
||||
* The configuration file must be a JSON file.
|
||||
*
|
||||
* @param path the path to the configuration file
|
||||
* @param file the path to the configuration file
|
||||
* @return a {@link Site} object initialized with the data from the configuration file
|
||||
* @throws IOException if an error occurs during file reading
|
||||
*/
|
||||
public static Site fromConfig(Path path) throws IOException {
|
||||
var load = new Load(LoadSettings.builder().build());
|
||||
var yaml = (Map<String, Object>) load.loadFromInputStream(Files.newInputStream(path));
|
||||
public static Site fromConfig(Path file) throws IOException {
|
||||
var yaml = YamlLoader.loadMap(file);
|
||||
|
||||
String name = (String) yaml.get("name");
|
||||
String baseUrl = (String) yaml.getOrDefault("baseUrl", "/");
|
||||
var templateArticles = (boolean) yaml.getOrDefault("templateArticles", false);
|
||||
var minify = (boolean) yaml.getOrDefault("minify", true);
|
||||
String name = (String) yaml.getOrDefault("name", DEFAULT.name());
|
||||
String baseUrl = (String) yaml.getOrDefault("baseUrl", DEFAULT.baseUrl());
|
||||
var templateArticles = (boolean) yaml.getOrDefault("templateArticles", DEFAULT.templateArticles());
|
||||
|
||||
String directory = "/";
|
||||
String directory = DEFAULT.directory();
|
||||
if (baseUrl != null) {
|
||||
var temp = baseUrl.substring(baseUrl.indexOf(':') + 3);
|
||||
var slashIndex = temp.indexOf('/');
|
||||
|
@ -62,7 +64,7 @@ public record Site(
|
|||
}
|
||||
|
||||
return new Site(
|
||||
name, baseUrl, directory, templateArticles, minify, yaml
|
||||
name, baseUrl, directory, templateArticles, yaml
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.blog;
|
||||
package eu.m724.blog.server;
|
||||
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
import com.sun.net.httpserver.SimpleFileServer;
|
14
src/main/java/eu/m724/blog/vc/Change.java
Normal file
14
src/main/java/eu/m724/blog/vc/Change.java
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright (c) 2025 blog-software-java developers
|
||||
* blog-software-java is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.blog.vc;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
public record Change(
|
||||
String author,
|
||||
ZonedDateTime time
|
||||
) { }
|
56
src/main/java/eu/m724/blog/vc/GitVersionControl.java
Normal file
56
src/main/java/eu/m724/blog/vc/GitVersionControl.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2025 blog-software-java developers
|
||||
* blog-software-java is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.blog.vc;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.RepositoryBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GitVersionControl implements VersionControl {
|
||||
private final Git git;
|
||||
private final Path directory;
|
||||
|
||||
public GitVersionControl(Path directory) throws IOException {
|
||||
this.directory = directory;
|
||||
|
||||
var repository = new RepositoryBuilder()
|
||||
.setGitDir(directory.resolve(".git").toFile())
|
||||
.build();
|
||||
|
||||
this.git = new Git(repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Change> getChanges(Path path) throws VersionControlException {
|
||||
path = path.normalize();
|
||||
if (path.startsWith(directory)) {
|
||||
path = directory.relativize(path);
|
||||
}
|
||||
|
||||
var changes = new ArrayList<Change>();
|
||||
|
||||
try {
|
||||
for (var commit : git.log().addPath(path.toString()).call()) {
|
||||
var author = commit.getAuthorIdent().getName();
|
||||
var time = Instant.ofEpochSecond(commit.getCommitTime()).atZone(ZoneOffset.UTC);
|
||||
|
||||
changes.add(new Change(author, time));
|
||||
}
|
||||
} catch (GitAPIException e) {
|
||||
throw new VersionControlException(e);
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
}
|
15
src/main/java/eu/m724/blog/vc/VersionControl.java
Normal file
15
src/main/java/eu/m724/blog/vc/VersionControl.java
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2025 blog-software-java developers
|
||||
* blog-software-java is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.blog.vc;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public interface VersionControl {
|
||||
List<Change> getChanges(Path path) throws VersionControlException;
|
||||
}
|
||||
|
15
src/main/java/eu/m724/blog/vc/VersionControlException.java
Normal file
15
src/main/java/eu/m724/blog/vc/VersionControlException.java
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Copyright (c) 2025 blog-software-java developers
|
||||
* blog-software-java is licensed under the GNU General Public License. See the LICENSE.md file
|
||||
* in the project root for the full license text.
|
||||
*/
|
||||
|
||||
package eu.m724.blog.vc;
|
||||
|
||||
// TODO runtime exception really?
|
||||
public class VersionControlException extends RuntimeException {
|
||||
public VersionControlException(Throwable cause) {
|
||||
// TODO maybe I need to raise message here too
|
||||
super(cause);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue