Skip to content

Commit

Permalink
Qute: make it possible to supply a template backed by a build item
Browse files Browse the repository at this point in the history
- resolves quarkusio#41386
  • Loading branch information
mkouba committed Jun 24, 2024
1 parent 32d0c2d commit d1d2803
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 16 deletions.
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,46 @@
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 {

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;

public TemplatePathBuildItem(String path, String extensionInfo, String content) {
this(path, content, null, Objects.requireNonNull(extensionInfo));
}

TemplatePathBuildItem(String path, Path fullPath, String content) {
this(path, content, Objects.requireNonNull(fullPath), null);
}

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

/**
* Uses the {@code /} path separator.
* The path relative to the template root. The {@code /} is used as a path separator.
* <p>
* 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 +49,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 @@ -51,8 +85,12 @@ public boolean isRegular() {
return !isTag();
}

public String getContent() {
return content;
public boolean isFileBased() {
return fullPath != null;
}

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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(new TemplatePathBuildItem("hi.txt", "test-ext", "Hello {name}!"));
}
}).produces(TemplatePathBuildItem.class)
.build();

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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(new TemplatePathBuildItem("foo/hello.txt", "test-ext", "Hello {name}!"));
}
}).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());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.nio.charset.Charset;
Expand Down Expand Up @@ -78,6 +79,7 @@ public class EngineProducer {
private final List<String> tags;
private final List<String> suffixes;
private final Set<String> templateRoots;
private final Map<String, String> templateContents;
private final Pattern templatePathExclude;
private final Locale defaultLocale;
private final Charset defaultCharset;
Expand All @@ -91,6 +93,7 @@ public EngineProducer(QuteContext context, QuteConfig config, QuteRuntimeConfig
this.contentTypes = contentTypes;
this.suffixes = config.suffixes;
this.templateRoots = context.getTemplateRoots();
this.templateContents = Map.copyOf(context.getTemplateContents());
this.tags = context.getTags();
this.templatePathExclude = config.templatePathExclude;
this.defaultLocale = locales.defaultLocale;
Expand Down Expand Up @@ -334,10 +337,11 @@ private Optional<TemplateLocation> locate(String path) {
if (templatePathExclude.matcher(path).matches()) {
return Optional.empty();
}
// First try to locate file-based templates
for (String templateRoot : templateRoots) {
URL resource = null;
String templatePath = templateRoot + path;
LOGGER.debugf("Locate template for %s", templatePath);
LOGGER.debugf("Locate template file for %s", templatePath);
resource = locatePath(templatePath);
if (resource == null) {
// Try path with suffixes
Expand All @@ -357,6 +361,25 @@ private Optional<TemplateLocation> locate(String path) {
return Optional.of(new ResourceTemplateLocation(resource, createVariant(templatePath)));
}
}
// Then try the template contents
LOGGER.debugf("Locate template contents for %s", path);
String content = templateContents.get(path);
if (path == null) {
// Try path with suffixes
for (String suffix : suffixes) {
String pathWithSuffix = path + "." + suffix;
if (templatePathExclude.matcher(pathWithSuffix).matches()) {
continue;
}
content = templateContents.get(pathWithSuffix);
if (content != null) {
break;
}
}
}
if (content != null) {
return Optional.of(new ContentTemplateLocation(content, createVariant(path)));
}
return Optional.empty();
}

Expand Down Expand Up @@ -439,7 +462,7 @@ static class ResourceTemplateLocation implements TemplateLocation {
private final URL resource;
private final Optional<Variant> variant;

public ResourceTemplateLocation(URL resource, Variant variant) {
ResourceTemplateLocation(URL resource, Variant variant) {
this.resource = resource;
this.variant = Optional.ofNullable(variant);
}
Expand Down Expand Up @@ -467,4 +490,26 @@ public Optional<Variant> getVariant() {

}

static class ContentTemplateLocation implements TemplateLocation {

private final String content;
private final Optional<Variant> variant;

ContentTemplateLocation(String content, Variant variant) {
this.content = content;
this.variant = Optional.ofNullable(variant);
}

@Override
public Reader read() {
return new StringReader(content);
}

@Override
public Optional<Variant> getVariant() {
return variant;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class QuteRecorder {

public Supplier<Object> createContext(List<String> resolverClasses,
List<String> templatePaths, List<String> tags, Map<String, List<String>> variants,
List<String> templateGlobalProviderClasses, Set<String> templateRoots) {
List<String> templateGlobalProviderClasses, Set<String> templateRoots, Map<String, String> templateContents) {
return new Supplier<Object>() {

@Override
Expand Down Expand Up @@ -48,6 +48,11 @@ public List<String> getTemplateGlobalProviderClasses() {
public Set<String> getTemplateRoots() {
return templateRoots;
}

@Override
public Map<String, String> getTemplateContents() {
return templateContents;
}
};
}
};
Expand All @@ -67,6 +72,8 @@ public interface QuteContext {

Set<String> getTemplateRoots();

Map<String, String> getTemplateContents();

}

}

0 comments on commit d1d2803

Please sign in to comment.