Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qute: make it possible to supply a template backed by a build item #41391

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2433,6 +2433,7 @@ void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecord

List<String> templates = new ArrayList<>();
List<String> tags = new ArrayList<>();
Map<String, String> templateContents = new HashMap<>();
for (TemplatePathBuildItem templatePath : templatePaths) {
if (templatePath.isTag()) {
// tags/myTag.html -> myTag.html
Expand All @@ -2441,6 +2442,9 @@ void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecord
} else {
templates.add(templatePath.getPath());
}
if (!templatePath.isFileBased()) {
templateContents.put(templatePath.getPath(), templatePath.getContent());
}
}
Map<String, List<String>> variants;
if (templateVariants.isPresent()) {
Expand All @@ -2454,7 +2458,7 @@ void initialize(BuildProducer<SyntheticBeanBuildItem> syntheticBeans, QuteRecord
.map(GeneratedValueResolverBuildItem::getClassName).collect(Collectors.toList()), templates,
tags, variants, templateInitializers.stream()
.map(TemplateGlobalProviderBuildItem::getClassName).collect(Collectors.toList()),
templateRoots.getPaths().stream().map(p -> p + "/").collect(Collectors.toSet())))
templateRoots.getPaths().stream().map(p -> p + "/").collect(Collectors.toSet()), templateContents))
.done());
}

Expand Down Expand Up @@ -3423,8 +3427,10 @@ private void checkDuplicatePaths(List<TemplatePathBuildItem> templatePaths) {
if (!duplicates.isEmpty()) {
StringBuilder builder = new StringBuilder("Duplicate templates found:");
for (Entry<String, List<TemplatePathBuildItem>> e : duplicates.entrySet()) {
builder.append("\n\t- ").append(e.getKey()).append(": ")
.append(e.getValue().stream().map(TemplatePathBuildItem::getFullPath).collect(Collectors.toList()));
builder.append("\n\t- ")
.append(e.getKey())
.append(": ")
.append(e.getValue().stream().map(TemplatePathBuildItem::getSourceInfo).collect(Collectors.toList()));
}
throw new IllegalStateException(builder.toString());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,59 @@
package io.quarkus.qute.deployment;

import java.nio.file.Path;
import java.util.Objects;

import io.quarkus.builder.item.MultiBuildItem;

/**
* Represents a template path.
* Discovered template.
* <p>
* Templates backed by files located in a template root are discovered automatically. Furthermore, extensions can produce this
* build item in order to provide a template that is not backed by a file.
*
* @see TemplateRootBuildItem
*/
public final class TemplatePathBuildItem extends MultiBuildItem {

/**
*
* @return a new builder instance
*/
public static Builder builder() {
return new Builder();
}

static final String TAGS = "tags/";

private final String path;
private final Path fullPath;
private final String content;
private final Path fullPath;
private final String extensionInfo;

/**
*
* @param path
* @param fullPath
* @param content
* @deprecated Use the {@link #builder()} instead
*/
@Deprecated
public TemplatePathBuildItem(String path, Path fullPath, String content) {
this(Objects.requireNonNull(path), Objects.requireNonNull(content), Objects.requireNonNull(fullPath), null);
}

private TemplatePathBuildItem(String path, String content, Path fullPath, String extensionInfo) {
this.path = path;
this.fullPath = fullPath;
this.content = content;
this.fullPath = fullPath;
this.extensionInfo = extensionInfo;
}

/**
* Uses the {@code /} path separator.
* The path relative to the template root. The {@code /} is used as a path separator.
* <p>
* The path must be unique, i.e. if there are multiple templates with the same path then the template analysis fails during
* build.
*
* @return the path relative to the template root
*/
Expand All @@ -31,14 +62,30 @@ public String getPath() {
}

/**
* Uses the system-dependent path separator.
* The full path of the template which uses the system-dependent path separator.
*
* @return the full path of the template
* @return the full path, or {@code null} for templates that are not backed by a file
*/
public Path getFullPath() {
return fullPath;
}

/**
*
* @return the content of the template
*/
public String getContent() {
return content;
}

/**
*
* @return the extension info
*/
public String getExtensionInfo() {
return extensionInfo;
}

/**
*
* @return {@code true} if it represents a user tag, {@code false} otherwise
Expand All @@ -47,12 +94,87 @@ public boolean isTag() {
return path.startsWith(TAGS);
}

/**
*
* @return {@code true} if it does not represent a tag, {@code false} otherwise
*/
public boolean isRegular() {
return !isTag();
}

public String getContent() {
return content;
/**
*
* @return {@code true} if it's backed by a file
*/
public boolean isFileBased() {
return fullPath != null;
}

public String getSourceInfo() {
return isFileBased() ? getFullPath().toString() : extensionInfo;
}

public static class Builder {

private String path;
private String content;
private Path fullPath;
private String extensionInfo;

/**
* Set the path relative to the template root. The {@code /} is used as a path separator.
* <p>
* The path must be unique, i.e. if there are multiple templates with the same path then the template analysis fails
* during build.
*
* @param path
* @return self
*/
public Builder path(String path) {
this.path = Objects.requireNonNull(path);
return this;
}

/**
* Set the content of the template.
*
* @param content
* @return self
*/
public Builder content(String content) {
this.content = Objects.requireNonNull(content);
return this;
}

/**
* Set the full path of the template for templates that are backed by a file.
*
* @param fullPath
* @return self
*/
public Builder fullPath(Path fullPath) {
this.fullPath = Objects.requireNonNull(fullPath);
return this;
}

/**
* Set the extension info for templates that are not backed by a file.
*
* @param info
* @return self
*/
public Builder extensionInfo(String info) {
this.extensionInfo = info;
return this;
}

public TemplatePathBuildItem build() {
if (fullPath == null && extensionInfo == null) {
throw new IllegalStateException("Templates that are not backed by a file must provide extension info");
}
return new TemplatePathBuildItem(path, content, fullPath, extensionInfo);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.quarkus.qute.deployment.builditemtemplate;

import static org.junit.jupiter.api.Assertions.fail;

import java.util.function.Consumer;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.qute.deployment.TemplatePathBuildItem;
import io.quarkus.test.QuarkusUnitTest;

public class AdditionalTemplatePathDuplicatesTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(root -> root
.addAsResource(new StringAsset("Hi {name}!"), "templates/hi.txt"))
.addBuildChainCustomizer(buildCustomizer())
.setExpectedException(IllegalStateException.class, true);

static Consumer<BuildChainBuilder> buildCustomizer() {
return new Consumer<BuildChainBuilder>() {
@Override
public void accept(BuildChainBuilder builder) {
builder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(TemplatePathBuildItem.builder()
.path("hi.txt")
.extensionInfo("test-ext")
.content("Hello {name}!").build());
}
}).produces(TemplatePathBuildItem.class)
.build();

builder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(TemplatePathBuildItem.builder()
.path("hi.txt")
.extensionInfo("test-ext")
.content("Hello {name}!").build());
}
}).produces(TemplatePathBuildItem.class)
.build();
}
};
}

@Test
public void test() {
fail();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.quarkus.qute.deployment.builditemtemplate;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.function.Consumer;

import jakarta.inject.Inject;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.qute.Engine;
import io.quarkus.qute.deployment.TemplatePathBuildItem;
import io.quarkus.test.QuarkusUnitTest;

public class AdditionalTemplatePathTest {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot(root -> root
.addAsResource(new StringAsset("Hi {name}!"), "templates/hi.txt")
.addAsResource(new StringAsset("And... {#include foo/hello /}"), "templates/include.txt"))
.addBuildChainCustomizer(buildCustomizer());

static Consumer<BuildChainBuilder> buildCustomizer() {
return new Consumer<BuildChainBuilder>() {
@Override
public void accept(BuildChainBuilder builder) {
builder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(TemplatePathBuildItem.builder()
.path("foo/hello.txt")
.extensionInfo("test-ext")
.content("Hello {name}!").build());
}
}).produces(TemplatePathBuildItem.class)
.build();

}
};
}

@Inject
Engine engine;

@Test
public void testTemplate() {
assertEquals("Hi M!", engine.getTemplate("hi").data("name", "M").render());
assertEquals("Hello M!", engine.getTemplate("foo/hello.txt").data("name", "M").render());
assertEquals("Hello M!", engine.getTemplate("foo/hello").data("name", "M").render());
assertEquals("And... Hello M!", engine.getTemplate("include").data("name", "M").render());
}

}
Loading