diff --git a/.gitignore b/.gitignore index e5ff508..82470d5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,12 +12,4 @@ eclipse/ *.iws .idea/ out/ -/bin/ -logs/ -.cache/ -node_modules/ -.parcel-cache/ -testing/output/ -testing/dist/ -testing/work/ -testing/gh-** \ No newline at end of file +testing/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c2f7212..9ee067e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.2.0] - 2023-12-24 + +### Added + +- Added sitemap generation. + ## [0.1.2] - 2023-12-23 ### Fixed @@ -25,7 +31,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Initial release. -[Unreleased]: https://github.com/refinedmods/refinedsites/compare/v0.1.2...HEAD +[Unreleased]: https://github.com/refinedmods/refinedsites/compare/v0.2.0...HEAD + +[0.2.0]: https://github.com/refinedmods/refinedsites/compare/v0.1.2...v0.2.0 [0.1.2]: https://github.com/refinedmods/refinedsites/compare/v0.1.1...v0.1.2 diff --git a/build.gradle b/build.gradle index 4b85262..2f0b759 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ dependencies { implementation 'org.kohsuke:github-api:1.318' implementation 'org.eclipse.jgit:org.eclipse.jgit:6.7.0.202309050840-r' implementation 'org.commonmark:commonmark:0.21.0' + implementation 'com.github.dfabulich:sitemapgen4j:1.1.2' } // https://github.com/ultraq/thymeleaf-layout-dialect/issues/220#issuecomment-944874151 diff --git a/src/main/java/com/refinedmods/refinedsites/RefinedSites.java b/src/main/java/com/refinedmods/refinedsites/RefinedSites.java index 259eba0..69b3a05 100644 --- a/src/main/java/com/refinedmods/refinedsites/RefinedSites.java +++ b/src/main/java/com/refinedmods/refinedsites/RefinedSites.java @@ -20,7 +20,7 @@ public static void main(final String[] args) { final SiteFactory siteFactory = new SiteFactory(rootPath); final Site site = siteFactory.getSite(); log.info("Loaded site {}", site); - final Renderer renderer = new Renderer(rootPath, rootPath.resolve("output/")); + final Renderer renderer = new Renderer(rootPath, rootPath.resolve("output/"), site.getUrl()); renderer.render(site); log.info("Done!"); } diff --git a/src/main/java/com/refinedmods/refinedsites/playbook/SiteFactory.java b/src/main/java/com/refinedmods/refinedsites/playbook/SiteFactory.java index d0dcc41..d26e799 100644 --- a/src/main/java/com/refinedmods/refinedsites/playbook/SiteFactory.java +++ b/src/main/java/com/refinedmods/refinedsites/playbook/SiteFactory.java @@ -62,11 +62,13 @@ public Site getSite() { private Map getReleases(final PlaybookConfig json) { final Map releasesByComponentName = new HashMap<>(); - for (final var releaseEntry : json.getReleases().entrySet()) { - final ReleaseConfig releaseConfig = releaseEntry.getValue(); - final String componentName = releaseEntry.getKey(); - final Releases releases = getReleases(componentName, releaseConfig); - releasesByComponentName.put(componentName, releases); + if (json.getReleases() != null) { + for (final var releaseEntry : json.getReleases().entrySet()) { + final ReleaseConfig releaseConfig = releaseEntry.getValue(); + final String componentName = releaseEntry.getKey(); + final Releases releases = getReleases(componentName, releaseConfig); + releasesByComponentName.put(componentName, releases); + } } return releasesByComponentName; } diff --git a/src/main/java/com/refinedmods/refinedsites/render/Renderer.java b/src/main/java/com/refinedmods/refinedsites/render/Renderer.java index c75c685..697dd98 100644 --- a/src/main/java/com/refinedmods/refinedsites/render/Renderer.java +++ b/src/main/java/com/refinedmods/refinedsites/render/Renderer.java @@ -15,10 +15,12 @@ import java.io.FileWriter; import java.io.IOException; +import java.net.MalformedURLException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,6 +31,10 @@ import com.github.slugify.Slugify; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.redfin.sitemapgenerator.ChangeFreq; +import com.redfin.sitemapgenerator.SitemapIndexGenerator; +import com.redfin.sitemapgenerator.WebSitemapGenerator; +import com.redfin.sitemapgenerator.WebSitemapUrl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import nz.net.ultraq.thymeleaf.layoutdialect.LayoutDialect; @@ -55,8 +61,10 @@ public class Renderer { private final Path sourcePath; private final Path outputPath; private final LinkBuilderImpl linkBuilder; + private final Date renderDate = new Date(); + private final String url; - public Renderer(final Path sourcePath, final Path outputPath) { + public Renderer(final Path sourcePath, final Path outputPath, final String url) { this.sourcePath = sourcePath; this.outputPath = outputPath; this.linkBuilder = new LinkBuilderImpl(outputPath); @@ -66,20 +74,29 @@ public Renderer(final Path sourcePath, final Path outputPath) { templateEngine.addDialect(new LayoutDialect()); templateEngine.setTemplateResolver(resolver); templateEngine.setLinkBuilder(linkBuilder); + this.url = url; } public void render(final Site site) { log.info("Rendering site {}", site); try { Files.createDirectories(outputPath); + final SitemapIndexGenerator sitemapIndex; + try { + sitemapIndex = new SitemapIndexGenerator(url, outputPath.resolve("sitemap_index.xml").toFile()); + } catch (final MalformedURLException e) { + throw new RuntimeException(e); + } final Path assetsPath = copyRootAssets(); renderReleasesIndex(site); for (final Component component : site.getComponents()) { renderComponentPre(component); } for (final Component component : site.getComponents()) { - renderComponent(component, assetsPath, site); + renderComponent(component, assetsPath, site, sitemapIndex); } + sitemapIndex.write(); + Files.writeString(outputPath.resolve("robots.txt"), "Sitemap: " + url + "/sitemap_index.xml"); } catch (IOException e) { throw new RuntimeException(e); } @@ -120,21 +137,19 @@ private void renderComponentPre(final Component component) { component.setSlug(componentSlug); } - private void renderComponent(final Component component, final Path assetsOutputPath, final Site site) + private void renderComponent(final Component component, + final Path assetsOutputPath, + final Site site, + final SitemapIndexGenerator sitemapIndex) throws IOException { log.info("Rendering component {}", component); - final Path componentOutputPath; - if (component.isRoot()) { - componentOutputPath = outputPath; - } else if (component.isLatest()) { - componentOutputPath = outputPath.resolve(component.getSlug()); - Files.createDirectories(componentOutputPath); - } else { - componentOutputPath = outputPath.resolve(component.getSlug()).resolve( - component.getVersion().friendlyName() - ); - Files.createDirectories(componentOutputPath); - } + final Path componentOutputPath = getComponentOutputPath(component); + final String sitemapBaseUrl = component.isRoot() ? url : url + "/" + outputPath.relativize(componentOutputPath); + final WebSitemapGenerator componentSitemap = getWebSitemapGenerator( + component, + sitemapBaseUrl, + componentOutputPath + ); if (component.getAssetsPath() != null) { copyComponentAssets(component, assetsOutputPath); } @@ -162,9 +177,45 @@ private void renderComponent(final Component component, final Path assetsOutputP site, pageInfo, parsedReleases, - releaseMatchingComponentVersion + releaseMatchingComponentVersion, + sitemapBaseUrl, + componentSitemap + ); + } + if (componentSitemap != null) { + componentSitemap.write(); + sitemapIndex.addUrl(sitemapBaseUrl + "/sitemap.xml", renderDate); + } + } + + private Path getComponentOutputPath(final Component component) throws IOException { + final Path componentOutputPath; + if (component.isRoot()) { + componentOutputPath = outputPath; + } else if (component.isLatest()) { + componentOutputPath = outputPath.resolve(component.getSlug()); + Files.createDirectories(componentOutputPath); + } else { + componentOutputPath = outputPath.resolve(component.getSlug()).resolve( + component.getVersion().friendlyName() ); + Files.createDirectories(componentOutputPath); + } + return componentOutputPath; + } + + @Nullable + private WebSitemapGenerator getWebSitemapGenerator(final Component component, + final String sitemapBaseUrl, + final Path componentOutputPath) { + if (component.isLatest()) { + try { + return new WebSitemapGenerator(sitemapBaseUrl, componentOutputPath.toFile()); + } catch (final MalformedURLException e) { + throw new RuntimeException(e); + } } + return null; } @Nullable @@ -320,7 +371,9 @@ private void renderPage(final Path pagePath, final Site site, final Map pageInfo, final List releases, - final ParsedRelease releaseMatchingComponentVersion) throws IOException { + final ParsedRelease releaseMatchingComponentVersion, + final String baseUrl, + @Nullable final WebSitemapGenerator sitemapGenerator) throws IOException { log.info("Rendering page {}", pagePath); final Context context = new Context(); final PageInfo info = pageInfo.get(pagePath); @@ -349,6 +402,11 @@ private void renderPage(final Path pagePath, linkBuilder.setCurrentPageOutputPath(pageOutputPath); final String template = getTemplate(pagePath); templateEngine.process(template, context, fileWriter); + if (sitemapGenerator != null && !info.relativePath().contains("404")) { + sitemapGenerator.addUrl(new WebSitemapUrl.Options( + baseUrl + "/" + componentOutputPath.relativize(pageOutputPath) + ).lastMod(renderDate).changeFreq(ChangeFreq.DAILY).build()); + } } private String getTemplate(final Path pagePath) {