From 99b3a5082408ad99dc89bbf82bb327444e3a2af6 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Wed, 9 Oct 2024 20:33:20 +0200 Subject: [PATCH] Allow other extensions such as json, yaml, xml, ... --- .../io/quarkiverse/roq/util/PathUtils.java | 5 ++ .../deployment/RoqPluginAliasesProcessor.java | 12 ++-- .../deployment/RoqPluginTaggingProcessor.java | 10 +-- .../deployment/RoqFrontMatterProcessor.java | 7 +- .../{Link.java => TemplateLink.java} | 29 +++++--- ...qFrontMatterDataModificationBuildItem.java | 2 +- .../data/RoqFrontMatterDataProcessor.java | 16 +++-- .../RoqFrontMatterPublishProcessor.java | 14 ++-- .../record/RoqFrontMatterInitProcessor.java | 13 ++-- .../RoqFrontMatterRawTemplateBuildItem.java | 4 +- .../scan/RoqFrontMatterScanProcessor.java | 69 ++++++++++++------- .../RoqFrontMatterApiModificationTest.java | 4 +- .../{LinkTest.java => TemplateLinkTest.java} | 20 ++++-- .../src/test/resources/application.properties | 1 + .../src/test/resources/site/beers.json | 52 ++++++++++++++ .../runtime/RoqFrontMatterRecorder.java | 2 +- .../roq/frontmatter/runtime/model/Page.java | 4 +- .../frontmatter/runtime/model/PageInfo.java | 27 ++++++-- .../src/main/resources/application.properties | 1 + 19 files changed, 204 insertions(+), 88 deletions(-) rename roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/{Link.java => TemplateLink.java} (74%) rename roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/{LinkTest.java => TemplateLinkTest.java} (53%) create mode 100644 roq-frontmatter/deployment/src/test/resources/site/beers.json create mode 100644 roq-frontmatter/runtime/src/main/resources/application.properties diff --git a/common/runtime/src/main/java/io/quarkiverse/roq/util/PathUtils.java b/common/runtime/src/main/java/io/quarkiverse/roq/util/PathUtils.java index 1c610bb3..f1a08f01 100644 --- a/common/runtime/src/main/java/io/quarkiverse/roq/util/PathUtils.java +++ b/common/runtime/src/main/java/io/quarkiverse/roq/util/PathUtils.java @@ -45,6 +45,11 @@ public static String removeExtension(String path) { return i > 0 ? path.substring(0, i) : path; } + public static String getExtension(String path) { + final int i = path.lastIndexOf("."); + return i > 0 ? path.substring(i + 1) : null; + } + public static String fileName(String path) { final int i = path.lastIndexOf("/"); if (i == -1) { diff --git a/plugin/aliases/deployment/src/main/java/io/quarkiverse/roq/plugin/aliases/deployment/RoqPluginAliasesProcessor.java b/plugin/aliases/deployment/src/main/java/io/quarkiverse/roq/plugin/aliases/deployment/RoqPluginAliasesProcessor.java index 573a94ee..5b3f0b7c 100644 --- a/plugin/aliases/deployment/src/main/java/io/quarkiverse/roq/plugin/aliases/deployment/RoqPluginAliasesProcessor.java +++ b/plugin/aliases/deployment/src/main/java/io/quarkiverse/roq/plugin/aliases/deployment/RoqPluginAliasesProcessor.java @@ -1,12 +1,8 @@ package io.quarkiverse.roq.plugin.aliases.deployment; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; -import io.quarkiverse.roq.frontmatter.deployment.Link; +import io.quarkiverse.roq.frontmatter.deployment.TemplateLink; import io.quarkiverse.roq.frontmatter.deployment.data.RoqFrontMatterTemplateBuildItem; import io.quarkiverse.roq.frontmatter.runtime.RoqSiteConfig; import io.quarkiverse.roq.frontmatter.runtime.model.RoqUrl; @@ -59,8 +55,8 @@ public void consumeTemplates( } RoqUrl url = item.url(); for (String alias : aliasesName) { - String aliasLink = Link.pageLink(config.rootPath(), alias, new Link.PageLinkData( - item.raw().info().baseFileName(), item.raw().info().date(), item.raw().collection(), item.data())); + String aliasLink = TemplateLink.pageLink(config.rootPath(), alias, new TemplateLink.PageLinkData( + item.raw().info(), item.raw().collection(), item.data())); aliasMap.put(aliasLink, url.path()); } } diff --git a/plugin/tagging/deployment/src/main/java/io/quarkiverse/roq/plugin/tagging/deployment/RoqPluginTaggingProcessor.java b/plugin/tagging/deployment/src/main/java/io/quarkiverse/roq/plugin/tagging/deployment/RoqPluginTaggingProcessor.java index b76244bd..caf638fc 100644 --- a/plugin/tagging/deployment/src/main/java/io/quarkiverse/roq/plugin/tagging/deployment/RoqPluginTaggingProcessor.java +++ b/plugin/tagging/deployment/src/main/java/io/quarkiverse/roq/plugin/tagging/deployment/RoqPluginTaggingProcessor.java @@ -9,9 +9,9 @@ import java.util.List; import java.util.Map; -import io.quarkiverse.roq.frontmatter.deployment.Link; -import io.quarkiverse.roq.frontmatter.deployment.Link.PageLinkData; import io.quarkiverse.roq.frontmatter.deployment.RoqFrontMatterRootUrlBuildItem; +import io.quarkiverse.roq.frontmatter.deployment.TemplateLink; +import io.quarkiverse.roq.frontmatter.deployment.TemplateLink.PageLinkData; import io.quarkiverse.roq.frontmatter.deployment.data.RoqFrontMatterDocumentTemplateBuildItem; import io.quarkiverse.roq.frontmatter.deployment.data.RoqFrontMatterPaginateTemplateBuildItem; import io.quarkiverse.roq.frontmatter.deployment.publish.RoqFrontMatterPublishDerivedCollectionBuildItem; @@ -67,7 +67,7 @@ void publishTagPages( // For all the tags we create a derivation: tag -> document ids for (String tag : tags) { derived.computeIfAbsent(tag, k -> new ArrayList<>()) - .add(document.raw().id()); + .add(document.raw().resolvedPath()); } } @@ -81,9 +81,9 @@ void publishTagPages( derivedCollectionProducer .produce(new RoqFrontMatterPublishDerivedCollectionBuildItem(tagCollection, e.getValue(), data)); - final String link = Link.pageLink(config.rootPath(), + final String link = TemplateLink.pageLink(config.rootPath(), data.getString(LINK_KEY, DEFAULT_TAGGING_COLLECTION_LINK_TEMPLATE), - new PageLinkData(item.info().baseFileName(), item.info().date(), tagCollection, data)); + new PageLinkData(item.info(), tagCollection, data)); final RoqUrl url = rootUrl.rootUrl().resolve(link); // Dealing with pagination is as simple as those two lines: if (data.containsKey(PAGINATE_KEY)) { diff --git a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/RoqFrontMatterProcessor.java b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/RoqFrontMatterProcessor.java index 0e312629..85c58cf8 100644 --- a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/RoqFrontMatterProcessor.java +++ b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/RoqFrontMatterProcessor.java @@ -41,6 +41,7 @@ void bindQuteTemplates( } final Set docTemplates = new HashSet<>(); final Set pageTemplates = new HashSet<>(); + final Set layoutTemplates = new HashSet<>(); // Produce generated Qute templates for (RoqFrontMatterRawTemplateBuildItem item : roqFrontMatterTemplates) { templatePathProducer @@ -52,6 +53,8 @@ void bindQuteTemplates( } else { pageTemplates.add(item.info().generatedTemplatePath()); } + } else { + layoutTemplates.add(item.info().generatedTemplatePath()); } } @@ -63,8 +66,10 @@ void bindQuteTemplates( } else if (pageTemplates.contains(c.getTemplateId())) { c.addParameter("page", NormalPage.class.getName()); c.addParameter("site", Site.class.getName()); + } else if (layoutTemplates.contains(c.getTemplateId())) { + c.addParameter("page", Page.class.getName()); + c.addParameter("site", Site.class.getName()); } - })); } diff --git a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/Link.java b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/TemplateLink.java similarity index 74% rename from roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/Link.java rename to roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/TemplateLink.java index 7b635f8c..3e9bbc77 100644 --- a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/Link.java +++ b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/TemplateLink.java @@ -12,10 +12,11 @@ import java.util.Optional; import java.util.function.Supplier; +import io.quarkiverse.roq.frontmatter.runtime.model.PageInfo; import io.quarkiverse.roq.util.PathUtils; import io.vertx.core.json.JsonObject; -public class Link { +public class TemplateLink { public static final String DEFAULT_PAGE_LINK_TEMPLATE = "/:name"; public static final String DEFAULT_PAGINATE_LINK_TEMPLATE = "/:collection/page:page"; private static final DateTimeFormatter YEAR_FORMAT = DateTimeFormatter.ofPattern("yyyy"); @@ -23,31 +24,37 @@ public class Link { private static final DateTimeFormatter DAY_FORMAT = DateTimeFormatter.ofPattern("dd"); public interface LinkData { - String baseFileName(); + PageInfo pageInfo(); String collection(); - ZonedDateTime date(); - JsonObject data(); } - public record PageLinkData(String baseFileName, ZonedDateTime date, String collection, + public record PageLinkData(PageInfo pageInfo, String collection, JsonObject data) implements LinkData { } - public record PaginateLinkData(String baseFileName, ZonedDateTime date, String collection, String page, + public record PaginateLinkData(PageInfo pageInfo, String collection, String page, JsonObject data) implements LinkData { } private static Map> withBasePlaceHolders(LinkData data, Map> other) { Map> result = new HashMap<>(Map.ofEntries( Map.entry(":collection", data::collection), - Map.entry(":year", () -> Optional.ofNullable(data.date()).orElse(ZonedDateTime.now()).format(YEAR_FORMAT)), - Map.entry(":month", () -> Optional.ofNullable(data.date()).orElse(ZonedDateTime.now()).format(MONTH_FORMAT)), - Map.entry(":day", () -> Optional.ofNullable(data.date()).orElse(ZonedDateTime.now()).format(DAY_FORMAT)), - Map.entry(":name", () -> slugify(data.baseFileName())), - Map.entry(":title", () -> data.data().getString("slug", slugify(data.baseFileName()))))); + Map.entry(":year", + () -> Optional.ofNullable(data.pageInfo().date()).orElse(ZonedDateTime.now()).format(YEAR_FORMAT)), + Map.entry(":month", + () -> Optional.ofNullable(data.pageInfo().date()).orElse(ZonedDateTime.now()).format(MONTH_FORMAT)), + Map.entry(":day", + () -> Optional.ofNullable(data.pageInfo().date()).orElse(ZonedDateTime.now()).format(DAY_FORMAT)), + Map.entry(":name", () -> { + if (data.pageInfo().isHtml()) { + return slugify(data.pageInfo().baseFileName()); + } + return slugify(data.pageInfo().baseFileName()) + "." + data.pageInfo().getExtension(); + }), + Map.entry(":title", () -> data.data().getString("slug", slugify(data.pageInfo().baseFileName()))))); if (other != null) { result.putAll(other); } diff --git a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/data/RoqFrontMatterDataModificationBuildItem.java b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/data/RoqFrontMatterDataModificationBuildItem.java index dd7be492..23b2bcd3 100644 --- a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/data/RoqFrontMatterDataModificationBuildItem.java +++ b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/data/RoqFrontMatterDataModificationBuildItem.java @@ -34,6 +34,6 @@ public int order() { public interface DataModifier { - JsonObject modify(String id, String templatePath, JsonObject fm); + JsonObject modify(String resolvedPath, String sourcePath, JsonObject fm); } } diff --git a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/data/RoqFrontMatterDataProcessor.java b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/data/RoqFrontMatterDataProcessor.java index 0d71841e..9a90ee57 100644 --- a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/data/RoqFrontMatterDataProcessor.java +++ b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/data/RoqFrontMatterDataProcessor.java @@ -1,6 +1,6 @@ package io.quarkiverse.roq.frontmatter.deployment.data; -import static io.quarkiverse.roq.frontmatter.deployment.Link.DEFAULT_PAGE_LINK_TEMPLATE; +import static io.quarkiverse.roq.frontmatter.deployment.TemplateLink.DEFAULT_PAGE_LINK_TEMPLATE; import java.util.List; import java.util.Map; @@ -8,8 +8,8 @@ import java.util.function.Function; import java.util.stream.Collectors; -import io.quarkiverse.roq.frontmatter.deployment.Link; import io.quarkiverse.roq.frontmatter.deployment.RoqFrontMatterRootUrlBuildItem; +import io.quarkiverse.roq.frontmatter.deployment.TemplateLink; import io.quarkiverse.roq.frontmatter.deployment.publish.RoqFrontMatterPublishPageBuildItem; import io.quarkiverse.roq.frontmatter.deployment.scan.RoqFrontMatterRawTemplateBuildItem; import io.quarkiverse.roq.frontmatter.runtime.RoqSiteConfig; @@ -35,14 +35,14 @@ void prepareData(HttpBuildTimeConfig httpConfig, } final var byKey = roqFrontMatterTemplates.stream() - .collect(Collectors.toMap(RoqFrontMatterRawTemplateBuildItem::id, Function.identity())); + .collect(Collectors.toMap(RoqFrontMatterRawTemplateBuildItem::resolvedPath, Function.identity())); final RootUrl rootUrl = new RootUrl(config.urlOptional().orElse(""), httpConfig.rootPath); rootUrlProducer.produce(new RoqFrontMatterRootUrlBuildItem(rootUrl)); for (RoqFrontMatterRawTemplateBuildItem item : roqFrontMatterTemplates) { JsonObject data = mergeParents(item, byKey); - final String link = Link.pageLink(config.rootPath(), data.getString(LINK_KEY, DEFAULT_PAGE_LINK_TEMPLATE), - new Link.PageLinkData(item.info().baseFileName(), item.info().date(), item.collection(), data)); + final String link = TemplateLink.pageLink(config.rootPath(), data.getString(LINK_KEY, DEFAULT_PAGE_LINK_TEMPLATE), + new TemplateLink.PageLinkData(item.info(), item.collection(), data)); RoqFrontMatterTemplateBuildItem templateItem = new RoqFrontMatterTemplateBuildItem(item, rootUrl.resolve(link), data); templatesProducer.produce(templateItem); @@ -86,7 +86,11 @@ public static JsonObject mergeParents(RoqFrontMatterRawTemplateBuildItem item, String parent = item.layout(); fms.add(item.data()); while (parent != null) { - if (byPath.containsKey(parent)) { + if (byPath.containsKey(parent + ".html")) { + final RoqFrontMatterRawTemplateBuildItem parentItem = byPath.get(parent + ".html"); + parent = parentItem.layout(); + fms.push(parentItem.data()); + } else if (byPath.containsKey(parent)) { final RoqFrontMatterRawTemplateBuildItem parentItem = byPath.get(parent); parent = parentItem.layout(); fms.push(parentItem.data()); diff --git a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/publish/RoqFrontMatterPublishProcessor.java b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/publish/RoqFrontMatterPublishProcessor.java index 00ee5e90..5eeb43a0 100644 --- a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/publish/RoqFrontMatterPublishProcessor.java +++ b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/publish/RoqFrontMatterPublishProcessor.java @@ -1,6 +1,6 @@ package io.quarkiverse.roq.frontmatter.deployment.publish; -import static io.quarkiverse.roq.frontmatter.deployment.Link.DEFAULT_PAGINATE_LINK_TEMPLATE; +import static io.quarkiverse.roq.frontmatter.deployment.TemplateLink.DEFAULT_PAGINATE_LINK_TEMPLATE; import static io.quarkiverse.roq.frontmatter.deployment.data.RoqFrontMatterDataProcessor.PAGINATE_KEY; import java.util.ArrayList; @@ -11,9 +11,9 @@ import org.jboss.logging.Logger; -import io.quarkiverse.roq.frontmatter.deployment.Link; import io.quarkiverse.roq.frontmatter.deployment.Paginate; import io.quarkiverse.roq.frontmatter.deployment.RoqFrontMatterRootUrlBuildItem; +import io.quarkiverse.roq.frontmatter.deployment.TemplateLink; import io.quarkiverse.roq.frontmatter.deployment.data.RoqFrontMatterDocumentTemplateBuildItem; import io.quarkiverse.roq.frontmatter.deployment.data.RoqFrontMatterPaginateTemplateBuildItem; import io.quarkiverse.roq.frontmatter.deployment.scan.RoqFrontMatterRawTemplateBuildItem; @@ -66,11 +66,11 @@ public void paginatePublish(RoqSiteConfig config, for (RoqFrontMatterPaginateTemplateBuildItem pagination : paginationList) { final RoqFrontMatterRawTemplateBuildItem item = pagination.raw(); final JsonObject data = pagination.data(); - Paginate paginate = readPaginate(item.id(), data, pagination.defaultPaginatedCollection()); + Paginate paginate = readPaginate(item.resolvedPath(), data, pagination.defaultPaginatedCollection()); AtomicInteger collectionSize = sizeByCollection.get(paginate.collection()); if (collectionSize == null) { throw new ConfigurationException( - "Paginate collection not found '" + paginate.collection() + "' in " + item.id()); + "Paginate collection not found '" + paginate.collection() + "' in " + item.resolvedPath()); } final int total = collectionSize.get(); if (paginate.size() <= 0) { @@ -85,14 +85,14 @@ public void paginatePublish(RoqSiteConfig config, if (i == 1) { paginatedUrl = pagination.url(); } else { - final String link = Link.paginateLink(config.rootPath(), linkTemplate, - new Link.PaginateLinkData(item.info().baseFileName(), item.info().date(), + final String link = TemplateLink.paginateLink(config.rootPath(), linkTemplate, + new TemplateLink.PaginateLinkData(item.info(), paginate.collection(), Integer.toString(i), data)); paginatedUrl = rootUrl.resolve(link); } PageInfo info = item.info(); if (i > 1) { - info = info.changeId(paginatedUrl.path()); + info = info.changeResolvedPath(paginatedUrl.path()); } paginatedPages.add(new PageToPublish(paginatedUrl, info, data)); } diff --git a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/record/RoqFrontMatterInitProcessor.java b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/record/RoqFrontMatterInitProcessor.java index 7c0b0b65..9b828162 100644 --- a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/record/RoqFrontMatterInitProcessor.java +++ b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/record/RoqFrontMatterInitProcessor.java @@ -57,8 +57,8 @@ void bindCollections( final Supplier document = recorder.createDocument(item.collection(), url, item.info(), item.data()); - documentsById.put(item.info().id(), document); - pagesProducer.produce(new RoqFrontMatterPageBuildItem(item.info().id(), url, document)); + documentsById.put(item.info().resolvedPath(), document); + pagesProducer.produce(new RoqFrontMatterPageBuildItem(item.info().resolvedPath(), url, document)); docs.add(document); } collectionsProducer.produce(new RoqFrontMatterCollectionBuildItem(e.getKey(), docs)); @@ -70,7 +70,7 @@ void bindCollections( for (String id : i.documentIds()) { final Supplier doc = documentsById.get(id); if (doc == null) { - throw new IllegalStateException("No document found for id " + id); + throw new IllegalStateException("No document found for resolvedPath " + id); } docs.add(doc); } @@ -93,9 +93,10 @@ void bindPages( for (RoqFrontMatterPublishPageBuildItem page : pages) { final Supplier recordedPage = recorder.createPage(page.url(), page.info(), page.data(), page.paginator()); - pagesProducer.produce(new RoqFrontMatterPageBuildItem(page.info().id(), page.url(), recordedPage)); - normalPagesProducer.produce(new RoqFrontMatterNormalPageBuildItem(page.info().id(), page.url(), recordedPage)); - if (page.info().id().equals("index")) { + pagesProducer.produce(new RoqFrontMatterPageBuildItem(page.info().resolvedPath(), page.url(), recordedPage)); + normalPagesProducer + .produce(new RoqFrontMatterNormalPageBuildItem(page.info().resolvedPath(), page.url(), recordedPage)); + if (page.info().resolvedPath().equals("index.html")) { indexPageProducer.produce(new RoqFrontMatterIndexPageBuildItem(recordedPage)); } } diff --git a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterRawTemplateBuildItem.java b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterRawTemplateBuildItem.java index b17a5fbf..9208bf60 100644 --- a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterRawTemplateBuildItem.java +++ b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterRawTemplateBuildItem.java @@ -64,8 +64,8 @@ public boolean isPage() { return isPage; } - public String id() { - return info.id(); + public String resolvedPath() { + return info.resolvedPath(); } public PageInfo info() { diff --git a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterScanProcessor.java b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterScanProcessor.java index 13f48b86..ca11ed59 100644 --- a/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterScanProcessor.java +++ b/roq-frontmatter/deployment/src/main/java/io/quarkiverse/roq/frontmatter/deployment/scan/RoqFrontMatterScanProcessor.java @@ -1,7 +1,6 @@ package io.quarkiverse.roq.frontmatter.deployment.scan; -import static io.quarkiverse.roq.util.PathUtils.removeExtension; -import static io.quarkiverse.roq.util.PathUtils.toUnixPath; +import static io.quarkiverse.roq.util.PathUtils.*; import static java.util.function.Predicate.not; import java.io.IOException; @@ -16,6 +15,7 @@ import java.util.*; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -30,14 +30,16 @@ import io.quarkiverse.roq.frontmatter.deployment.RoqFrontMatterConfig; import io.quarkiverse.roq.frontmatter.deployment.data.RoqFrontMatterDataModificationBuildItem; import io.quarkiverse.roq.frontmatter.runtime.model.PageInfo; +import io.quarkiverse.roq.util.PathUtils; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem; +import io.quarkus.qute.runtime.QuteConfig; import io.vertx.core.json.JsonObject; public class RoqFrontMatterScanProcessor { private static final Logger LOGGER = org.jboss.logging.Logger.getLogger(RoqFrontMatterScanProcessor.class); - private static final Set SUPPORTED_EXTENSIONS = Set.of(".md", "markdown", ".html", ".asciidoc", ".adoc"); + private static final Set SUPPORTED_LAYOUT_EXTENSIONS = Set.of(".md", "markdown", ".html", ".asciidoc", ".adoc"); public static final Pattern FRONTMATTER_PATTERN = Pattern.compile("^---\\v.*\\v---\\v", Pattern.DOTALL); private static final String DRAFT_KEY = "draft"; private static final String DATE_KEY = "date"; @@ -58,13 +60,13 @@ public String apply(String content) { return open + "\n" + content.strip() + "\n" + close; } - public static Function find(String fileName) { - if (!fileName.contains(".")) { - return Function.identity(); + public static Function find(String fileName, Function defaultFunction) { + final String extension = PathUtils.getExtension(fileName); + if (extension == null) { + return defaultFunction; } - final String extension = fileName.substring(fileName.lastIndexOf(".") + 1); if (!MARKUP_BY_EXT.containsKey(extension)) { - return Function.identity(); + return defaultFunction; } return MARKUP_BY_EXT.get(extension)::apply; } @@ -72,6 +74,7 @@ public static Function find(String fileName) { @BuildStep void scan(RoqProjectBuildItem roqProject, + QuteConfig quteConfig, RoqJacksonBuildItem jackson, BuildProducer dataProducer, List dataModifications, @@ -79,7 +82,8 @@ void scan(RoqProjectBuildItem roqProject, BuildProducer watch) { try { dataModifications.sort(Comparator.comparing(RoqFrontMatterDataModificationBuildItem::order)); - Set items = resolveItems(roqProject, jackson.getYamlMapper(), roqDataConfig, + Set items = resolveItems(roqProject, quteConfig, jackson.getYamlMapper(), + roqDataConfig, watch, dataModifications); for (RoqFrontMatterRawTemplateBuildItem item : items) { @@ -91,10 +95,10 @@ void scan(RoqProjectBuildItem roqProject, } } - public Set resolveItems(RoqProjectBuildItem roqProject, YAMLMapper mapper, + public Set resolveItems(RoqProjectBuildItem roqProject, QuteConfig quteConfig, + YAMLMapper mapper, RoqFrontMatterConfig roqDataConfig, BuildProducer watch, List dataModifications) throws IOException { - HashSet items = new HashSet<>(); roqProject.consumeRoqDir(site -> { if (Files.isDirectory(site)) { @@ -105,7 +109,7 @@ public Set resolveItems(RoqProjectBuildItem try (Stream stream = Files.walk(dir)) { stream .filter(Files::isRegularFile) - .filter(RoqFrontMatterScanProcessor::isExtensionSupported) + .filter(RoqFrontMatterScanProcessor::isExtensionSupportedForLayout) .forEach(addBuildItem(dir, items, mapper, roqDataConfig, dataModifications, watch, null, false)); } catch (IOException e) { @@ -122,7 +126,7 @@ public Set resolveItems(RoqProjectBuildItem try (Stream stream = Files.walk(dir)) { stream .filter(Files::isRegularFile) - .filter(RoqFrontMatterScanProcessor::isExtensionSupported) + .filter(RoqFrontMatterScanProcessor.isExtensionSupportedForTemplate(quteConfig)) .forEach(addBuildItem(dir, items, mapper, roqDataConfig, dataModifications, watch, collectionsDir.getValue(), true)); @@ -136,7 +140,7 @@ public Set resolveItems(RoqProjectBuildItem try (Stream stream = Files.walk(site)) { stream .filter(Files::isRegularFile) - .filter(RoqFrontMatterScanProcessor::isExtensionSupported) + .filter(RoqFrontMatterScanProcessor.isExtensionSupportedForTemplate(quteConfig)) .filter(not(RoqFrontMatterScanProcessor::isFileExcluded)) .forEach(addBuildItem(site, items, mapper, roqDataConfig, dataModifications, watch, null, true)); } catch (IOException e) { @@ -161,8 +165,7 @@ private static Consumer addBuildItem(Path root, watch.produce(HotDeploymentWatchedFileBuildItem.builder().setLocation(file.toAbsolutePath().toString()).build()); String sourcePath = toUnixPath( collection != null ? collection + "/" + root.relativize(file) : root.relativize(file).toString()); - String templatePath = removeExtension(sourcePath) + ".html"; - final String id = removeExtension(templatePath); + String resolvedPath = removeExtension(sourcePath) + resolveOutputExtension(sourcePath); try { final String fullContent = Files.readString(file, StandardCharsets.UTF_8); if (hasFrontMatter(fullContent)) { @@ -170,7 +173,7 @@ private static Consumer addBuildItem(Path root, final Map map = mapper.convertValue(rootNode, Map.class); JsonObject fm = new JsonObject(map); for (RoqFrontMatterDataModificationBuildItem modification : dataModifications) { - fm = modification.modifier().modify(id, templatePath, fm); + fm = modification.modifier().modify(resolvedPath, sourcePath, fm); } final boolean draft = fm.getBoolean(DRAFT_KEY, false); if (!config.draft() && draft) { @@ -180,15 +183,15 @@ private static Consumer addBuildItem(Path root, final String content = stripFrontMatter(fullContent); String dateString = parsePublishDate(file, fm, config); - PageInfo info = new PageInfo(id, draft, config.imagesPath(), dateString, content, - sourcePath, templatePath); - LOGGER.debugf("Creating generated template for %s" + templatePath); + PageInfo info = PageInfo.create(resolvedPath, draft, config.imagesPath(), dateString, content, + sourcePath); + LOGGER.debugf("Creating generated template for %s" + resolvedPath); final String generatedTemplate = generateTemplate(sourcePath, layout, content); items.add( new RoqFrontMatterRawTemplateBuildItem(info, layout, isPage, fm, collection, generatedTemplate, isPage)); } else { - PageInfo info = new PageInfo(id, false, config.imagesPath(), null, fullContent, sourcePath, templatePath); + PageInfo info = PageInfo.create(resolvedPath, false, config.imagesPath(), null, fullContent, sourcePath); items.add( new RoqFrontMatterRawTemplateBuildItem(info, null, isPage, new JsonObject(), collection, fullContent, @@ -231,7 +234,7 @@ private static String generateTemplate(String fileName, String layout, String co if (layout != null) { template.append("{#include ").append(layout).append("}\n"); } - template.append(QuteMarkupSection.find(fileName).apply(content)); + template.append(QuteMarkupSection.find(fileName, Function.identity()).apply(content)); template.append("\n{/include}"); return template.toString(); } @@ -243,14 +246,32 @@ private static String normalizedLayout(String layout) { return removeExtension(layout); } + private static String resolveOutputExtension(String fileName) { + if (QuteMarkupSection.find(fileName, null) == null) { + final String extension = getExtension(fileName); + if (extension == null) { + return ""; + } + return "." + extension; + } + return ".html"; + } + private static boolean isFileExcluded(Path path) { String p = toUnixPath(path.toString()); return p.startsWith("_") || p.contains("/_"); } - private static boolean isExtensionSupported(Path path) { + private static boolean isExtensionSupportedForLayout(Path path) { String fileName = path.getFileName().toString(); - return SUPPORTED_EXTENSIONS.stream().anyMatch(fileName::endsWith); + return SUPPORTED_LAYOUT_EXTENSIONS.stream().anyMatch(fileName::endsWith); + } + + private static Predicate isExtensionSupportedForTemplate(QuteConfig quteConfig) { + return (p) -> { + String fileName = p.getFileName().toString(); + return isExtensionSupportedForLayout(p) || quteConfig.suffixes.stream().anyMatch(fileName::endsWith); + }; } private static String getFrontMatter(String content) { diff --git a/roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/RoqFrontMatterApiModificationTest.java b/roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/RoqFrontMatterApiModificationTest.java index bbc853c1..67873fc1 100644 --- a/roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/RoqFrontMatterApiModificationTest.java +++ b/roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/RoqFrontMatterApiModificationTest.java @@ -23,8 +23,8 @@ public class RoqFrontMatterApiModificationTest { @Override public void execute(BuildContext context) { - context.produce(new RoqFrontMatterDataModificationBuildItem((id, path, data) -> { - if (id.equals("pages/some-page")) { + context.produce(new RoqFrontMatterDataModificationBuildItem((resolvedPath, sourcePath, data) -> { + if (resolvedPath.equals("pages/some-page.html")) { final JsonObject newData = data.copy(); newData.put("some-text", "modified text"); newData.put("link", "/somewhere-else"); diff --git a/roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/LinkTest.java b/roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/TemplateLinkTest.java similarity index 53% rename from roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/LinkTest.java rename to roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/TemplateLinkTest.java index 4b3756ce..f1febf3c 100644 --- a/roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/LinkTest.java +++ b/roq-frontmatter/deployment/src/test/java/io/quarkiverse/roq/frontmatter/deployment/TemplateLinkTest.java @@ -1,24 +1,27 @@ package io.quarkiverse.roq.frontmatter.deployment; -import static io.quarkiverse.roq.frontmatter.deployment.Link.*; +import static io.quarkiverse.roq.frontmatter.deployment.TemplateLink.*; import static org.junit.jupiter.api.Assertions.*; import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import org.junit.jupiter.api.Test; +import io.quarkiverse.roq.frontmatter.runtime.model.PageInfo; import io.vertx.core.json.JsonObject; -class LinkTest { +class TemplateLinkTest { @Test void testLink() { JsonObject frontMatter = new JsonObject() .put("title", "My First Blog Post"); - - String generatedLink = pageLink("/", ":year/:month/:day/:title", new PageLinkData("my-first-blog-post", - ZonedDateTime.parse("2024-08-27T10:15:30+01:00[Europe/Paris]"), null, frontMatter)); + final PageInfo pageInfo = PageInfo.create("posts/my-first-blog-post.html", false, "images", + ZonedDateTime.parse("2024-08-27T10:15:30+01:00[Europe/Paris]").format(DateTimeFormatter.ISO_ZONED_DATE_TIME), + "", "_posts/my-first-blog-post.md"); + String generatedLink = pageLink("/", ":year/:month/:day/:title", new PageLinkData(pageInfo, null, frontMatter)); assertEquals("2024/08/27/my-first-blog-post", generatedLink); } @@ -27,8 +30,11 @@ void testPaginateLink() { JsonObject frontMatter = new JsonObject() .put("title", "My First Blog Post"); - String generatedLink = paginateLink("/", DEFAULT_PAGINATE_LINK_TEMPLATE, new PaginateLinkData("my-first-blog-post", - ZonedDateTime.parse("2024-08-27T10:15:30+01:00[Europe/Paris]"), "posts", "3", frontMatter)); + final PageInfo pageInfo = PageInfo.create("posts/my-first-blog-post.html", false, "images", + ZonedDateTime.parse("2024-08-27T10:15:30+01:00[Europe/Paris]").format(DateTimeFormatter.ISO_ZONED_DATE_TIME), + "", "_posts/my-first-blog-post.md"); + String generatedLink = paginateLink("/", DEFAULT_PAGINATE_LINK_TEMPLATE, + new PaginateLinkData(pageInfo, "posts", "3", frontMatter)); assertEquals("posts/page3", generatedLink); } } diff --git a/roq-frontmatter/deployment/src/test/resources/application.properties b/roq-frontmatter/deployment/src/test/resources/application.properties index 7ce75fd7..ba4dd7dc 100644 --- a/roq-frontmatter/deployment/src/test/resources/application.properties +++ b/roq-frontmatter/deployment/src/test/resources/application.properties @@ -1,3 +1,4 @@ + quarkus.http.root-path=/foo/ quarkus.roq.site.url=https://mywebsite.com diff --git a/roq-frontmatter/deployment/src/test/resources/site/beers.json b/roq-frontmatter/deployment/src/test/resources/site/beers.json new file mode 100644 index 00000000..1302f960 --- /dev/null +++ b/roq-frontmatter/deployment/src/test/resources/site/beers.json @@ -0,0 +1,52 @@ +{ + "beers": [ + { + "name": "Lagunitas IPA", + "brewery": "Lagunitas Brewing Company", + "style": "IPA", + "abv": 6.2, + "description": "A well-rounded, highly drinkable IPA with hints of citrus and floral notes.", + "origin": "California, USA" + }, + { + "name": "Sierra Nevada Pale Ale", + "brewery": "Sierra Nevada Brewing Co.", + "style": "Pale Ale", + "abv": 5.6, + "description": "A classic pale ale known for its piney and citrusy hop character.", + "origin": "California, USA" + }, + { + "name": "Guinness Draught", + "brewery": "Guinness", + "style": "Stout", + "abv": 4.2, + "description": "A rich, creamy stout with hints of coffee and chocolate.", + "origin": "Dublin, Ireland" + }, + { + "name": "Heineken", + "brewery": "Heineken", + "style": "Lager", + "abv": 5.0, + "description": "A crisp, refreshing lager with mild bitterness and a slightly sweet finish.", + "origin": "Amsterdam, Netherlands" + }, + { + "name": "Hoegaarden", + "brewery": "Hoegaarden Brewery", + "style": "Wheat Beer", + "abv": 4.9, + "description": "A light and refreshing Belgian wheat beer with notes of orange peel and coriander.", + "origin": "Hoegaarden, Belgium" + }, + { + "name": "Duvel", + "brewery": "Duvel Moortgat Brewery", + "style": "Belgian Strong Ale", + "abv": 8.5, + "description": "A strong, golden ale with a complex blend of fruity, spicy, and hoppy flavors.", + "origin": "Puurs, Belgium" + } + ] +} \ No newline at end of file diff --git a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/RoqFrontMatterRecorder.java b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/RoqFrontMatterRecorder.java index 282e62a4..1afa09e6 100644 --- a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/RoqFrontMatterRecorder.java +++ b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/RoqFrontMatterRecorder.java @@ -58,7 +58,7 @@ public Supplier createSite(RootUrl rootUrl, Supplier indexPage for (Supplier pagesSupplier : normalPagesSuppliers) { pages.add(pagesSupplier.get()); } - return new Site(rootUrl, indexPage.get().url(), rootUrl.resolve(indexPage.get().info().imagesPath()), + return new Site(rootUrl, indexPage.get().url(), rootUrl.resolve(indexPage.get().info().imagesRootPath()), indexPage.get().data(), pages, roqCollectionsSupplier.get()); }; } diff --git a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/Page.java b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/Page.java index e150fde9..1d68950a 100644 --- a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/Page.java +++ b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/Page.java @@ -14,7 +14,7 @@ public interface Page { PageInfo info(); default String id() { - return info().id(); + return info().resolvedPath(); } default String rawContent() { @@ -50,7 +50,7 @@ default RoqUrl img() { if (img == null) { return null; } - return url().rootUrl().resolve(info().imagesPath()).resolve(img); + return url().rootUrl().resolve(info().imagesRootPath()).resolve(img); } RoqUrl url(); diff --git a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/PageInfo.java b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/PageInfo.java index 29a9a7a4..4e308e1d 100644 --- a/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/PageInfo.java +++ b/roq-frontmatter/runtime/src/main/java/io/quarkiverse/roq/frontmatter/runtime/model/PageInfo.java @@ -11,16 +11,19 @@ @Vetoed public record PageInfo( /** - * The page id (e.g. posts/my-favorite-beer) + * The page resolve path (e.g. posts/my-favorite-beer.html) */ - String id, + String resolvedPath, /** * If this page is a draft or not */ boolean draft, - String imagesPath, + /** + * Where the images for this page are based (e.g. /static/images) + */ + String imagesRootPath, /** * This page zoned date @@ -42,8 +45,14 @@ public record PageInfo( */ String generatedTemplatePath) { - public PageInfo changeId(String id) { - return new PageInfo(id, draft(), imagesPath(), dateString(), rawContent(), sourcePath(), + public static PageInfo create(String resolvedPath, boolean draft, String imagesRootPath, String dateString, + String rawContent, + String sourcePath) { + return new PageInfo(resolvedPath, draft, imagesRootPath, dateString, rawContent, sourcePath, resolvedPath); + } + + public PageInfo changeResolvedPath(String resolvedPath) { + return new PageInfo(resolvedPath, draft(), imagesRootPath(), dateString(), rawContent(), sourcePath(), generatedTemplatePath()); } @@ -65,4 +74,12 @@ public String baseFileName() { return PathUtils.removeExtension(sourceFileName()); } + public String getExtension() { + return PathUtils.getExtension(sourceFileName()); + } + + public boolean isHtml() { + return "html".equalsIgnoreCase(getExtension()); + } + } diff --git a/roq-frontmatter/runtime/src/main/resources/application.properties b/roq-frontmatter/runtime/src/main/resources/application.properties new file mode 100644 index 00000000..0bc04261 --- /dev/null +++ b/roq-frontmatter/runtime/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.qute.suffixes=qute.html,qute.txt,html,txt,json,yaml,yml,xml \ No newline at end of file