From 40688391bfd52c2548a1d44305d5d7bcb33bd996 Mon Sep 17 00:00:00 2001 From: Matheus Cruz Date: Wed, 3 Jul 2024 14:51:54 -0300 Subject: [PATCH] Add markdown extension --- docs/modules/ROOT/nav.adoc | 1 + docs/modules/ROOT/pages/markdown.adoc | 107 +++++++++++++++ examples/markdown-example/pom.xml | 124 ++++++++++++++++++ .../src/main/resources/application.properties | 1 + .../resources/templates/pub/markdown.html | 15 +++ markdown/deployment/pom.xml | 46 +++++++ .../deployment/QuteWebMarkdownProcessor.java | 22 ++++ .../markdown/test/QuarkusMarkdownTest.java | 66 ++++++++++ .../test/QuteMarkdownSectionHelperTest.java | 33 +++++ ...teMarkdownSectionWithInnerSectionTest.java | 53 ++++++++ markdown/pom.xml | 20 +++ markdown/runtime/pom.xml | 57 ++++++++ .../runtime/MarkdownSectionHelperFactory.java | 40 ++++++ .../commonmark/CommonMarkConverter.java | 16 +++ .../resources/META-INF/quarkus-extension.yaml | 15 +++ pom.xml | 2 + 16 files changed, 618 insertions(+) create mode 100644 docs/modules/ROOT/pages/markdown.adoc create mode 100644 examples/markdown-example/pom.xml create mode 100644 examples/markdown-example/src/main/resources/application.properties create mode 100644 examples/markdown-example/src/main/resources/templates/pub/markdown.html create mode 100644 markdown/deployment/pom.xml create mode 100644 markdown/deployment/src/main/java/io/quarkiverse/qute/web/markdown/deployment/QuteWebMarkdownProcessor.java create mode 100644 markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuarkusMarkdownTest.java create mode 100644 markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionHelperTest.java create mode 100644 markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionWithInnerSectionTest.java create mode 100644 markdown/pom.xml create mode 100644 markdown/runtime/pom.xml create mode 100644 markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/MarkdownSectionHelperFactory.java create mode 100644 markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/commonmark/CommonMarkConverter.java create mode 100644 markdown/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 7963900..5daaec9 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -1 +1,2 @@ * xref:index.adoc[Quarkus Qute Web] +* xref:index.adoc[Quarkus Qute Markdown] diff --git a/docs/modules/ROOT/pages/markdown.adoc b/docs/modules/ROOT/pages/markdown.adoc new file mode 100644 index 0000000..6324f69 --- /dev/null +++ b/docs/modules/ROOT/pages/markdown.adoc @@ -0,0 +1,107 @@ += Quarkus Qute Markdown + +include::./includes/attributes.adoc[] + +The goal of this extension is to provide a simple way to render Markdown templates using Qute. It adds a new Qute section +named `markdown` or `md` that can be used to convert the content of the section to HTML using a Markdown processor. + +== Installation + +To install the extension, add the following dependency to your project: + +[source,xml,subs=attributes+] +---- + + io.quarkiverse.qute-markdown + quarkus-qute-web-markdown + 999-SNAPSHOT + +---- + +== Usage + +This extension can be used to render Markdown templates inside a Qute template or in a web application using the `quarkus-qute-web` extension. + +=== Standalone Usage + +Create a new Qute template with `foo.txt` name. + +[source,html] +---- + +{#md} +# Hello World +{/md} +---- + +Create a new resource class and inject the template using the `@Inject` annotation. Then, use the `render` method to render the template. +[source,java] +---- +package com.foo; + +import io.quarkus.qute.Template; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/hello") +public class GreetingResource { + + @Inject + Template foo; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return foo.render(); + } +} +---- + +Finally, start your application and open the following URL in your browser: `http://localhost:8080/hello`. + +The output should be the following: + +[source,html] +---- +

Hello World

+---- + +=== Integration with Qute Web + +This extension can be combined with the `quarkus-qute-web` extension to render Markdown templates in a web application. +Add the `quarkus-qute-web` extension to your project, inside the `pom.xml` file: + +[source,xml,subs=attributes+] +---- + + io.quarkiverse.qute.web + quarkus-qute-web + ${project.version} + +---- + +Then, create a new Qute template with the `.txt` or `.html` extension. + +[source,html] +---- + + + + + + My Qute Page with Markdown + + +

Hello World!

+ {#md} + # This is a Markdown section + It will be converted to HTML using a Markdown processor. + {/md} + + +---- + +Finally, start your application and open the following URL in your browser: `http://localhost:8080/markdown`. \ No newline at end of file diff --git a/examples/markdown-example/pom.xml b/examples/markdown-example/pom.xml new file mode 100644 index 0000000..7d635c7 --- /dev/null +++ b/examples/markdown-example/pom.xml @@ -0,0 +1,124 @@ + + + 4.0.0 + io.quarkiverse.qute.web + quarkus-qute-web-restclient-example + 1.0.0-SNAPSHOT + + 3.10.1 + 17 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus + 3.6.6 + 3.0.0 + 999-SNAPSHOT + true + 3.0.0-M7 + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + io.quarkus + quarkus-rest-client-reactive-jackson + + + io.quarkiverse.qute.web + quarkus-qute-web + ${qute.web.version} + + + io.quarkiverse.qute.web + quarkus-qute-web-markdown + ${qute.web.markdown.version} + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + + -parameters + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + + ${project.build.directory}/${project.build.finalName}-runner + + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + native + + + native + + + + false + native + + + + diff --git a/examples/markdown-example/src/main/resources/application.properties b/examples/markdown-example/src/main/resources/application.properties new file mode 100644 index 0000000..d89858e --- /dev/null +++ b/examples/markdown-example/src/main/resources/application.properties @@ -0,0 +1 @@ +quarkus.tls.trust-all=true \ No newline at end of file diff --git a/examples/markdown-example/src/main/resources/templates/pub/markdown.html b/examples/markdown-example/src/main/resources/templates/pub/markdown.html new file mode 100644 index 0000000..1ae0198 --- /dev/null +++ b/examples/markdown-example/src/main/resources/templates/pub/markdown.html @@ -0,0 +1,15 @@ + + + + + + My Qute Page with Markdown + + +

Hello World!

+{#md} +## This is a Markdown section +It will be converted to HTML using a Markdown processor. +{/md} + + \ No newline at end of file diff --git a/markdown/deployment/pom.xml b/markdown/deployment/pom.xml new file mode 100644 index 0000000..62d1fb5 --- /dev/null +++ b/markdown/deployment/pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + + + io.quarkiverse.qute.web + quarkus-qute-web-markdown-parent + 999-SNAPSHOT + + quarkus-qute-web-markdown-deployment + Quarkus Qute Web - Markdown - Deployment + + + + io.quarkiverse.qute.web + quarkus-qute-web-markdown + ${project.version} + + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-qute-deployment + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/markdown/deployment/src/main/java/io/quarkiverse/qute/web/markdown/deployment/QuteWebMarkdownProcessor.java b/markdown/deployment/src/main/java/io/quarkiverse/qute/web/markdown/deployment/QuteWebMarkdownProcessor.java new file mode 100644 index 0000000..121e959 --- /dev/null +++ b/markdown/deployment/src/main/java/io/quarkiverse/qute/web/markdown/deployment/QuteWebMarkdownProcessor.java @@ -0,0 +1,22 @@ +package io.quarkiverse.qute.web.markdown.deployment; + +import io.quarkiverse.qute.web.markdown.runtime.MarkdownSectionHelperFactory; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +class QuteWebMarkdownProcessor { + + private static final String FEATURE = "qute-web-markdown"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + void process(BuildProducer additionalBeans) { + additionalBeans.produce(new AdditionalBeanBuildItem(MarkdownSectionHelperFactory.class)); + } +} diff --git a/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuarkusMarkdownTest.java b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuarkusMarkdownTest.java new file mode 100644 index 0000000..98a1f64 --- /dev/null +++ b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuarkusMarkdownTest.java @@ -0,0 +1,66 @@ +package io.quarkiverse.qute.web.markdown.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkiverse.qute.web.markdown.runtime.MarkdownSectionHelperFactory; +import io.quarkus.qute.Engine; + +public class QuarkusMarkdownTest { + + @Test + public void testMd() { + Engine engine = Engine.builder().addDefaults() + .addSectionHelper(new MarkdownSectionHelperFactory()).build(); + assertEquals("

...

\n", engine.parse("{#md}...{/md}").render()); + } + + @Test + public void testMarkdown() { + Engine engine = Engine.builder().addDefaults() + .addSectionHelper(new MarkdownSectionHelperFactory()).build(); + assertEquals("

...

\n", engine.parse("{#markdown}...{/markdown}").render()); + } + + @Test + public void testH1() { + Engine engine = Engine.builder().addDefaults() + .addSectionHelper(new MarkdownSectionHelperFactory()).build(); + assertEquals("

Quarkus and Roq

\n", engine.parse("{#md}# Quarkus and Roq{/md}").render()); + } + + @Test + void testJsonObjectValueResolver() { + + Engine engine = Engine.builder().addDefaults() + .addSectionHelper(new MarkdownSectionHelperFactory()).build(); + + String result = engine.parse(""" +

Quarkus and Qute

+ {#md} + # Qute and Roq + Here is a list: + {#for item in items} + - an {item} as a list item + {/for} + {/md} + """).data("items", List.of("apple", "banana", "cherry")) + .render(); + + Assertions.assertEquals(""" +

Quarkus and Qute

+

Qute and Roq

+

Here is a list:

+ + """, result); + + } +} diff --git a/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionHelperTest.java b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionHelperTest.java new file mode 100644 index 0000000..e0a213f --- /dev/null +++ b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionHelperTest.java @@ -0,0 +1,33 @@ +package io.quarkiverse.qute.web.markdown.test; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.Template; +import io.quarkus.test.QuarkusUnitTest; + +public class QuteMarkdownSectionHelperTest { + + @RegisterExtension + static final QuarkusUnitTest quarkusApp = new QuarkusUnitTest() + .withApplicationRoot( + app -> app.addAsResource(new StringAsset( + "{#markdown}# Qute and Roq{/markdown}"), + "templates/foo.txt")); + + @Inject + Template foo; + + @Test + void testJsonObjectValueResolver() { + String result = foo.render(); + + System.out.println(result); + + Assertions.assertEquals("

Qute and Roq

\n", result); + } +} diff --git a/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionWithInnerSectionTest.java b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionWithInnerSectionTest.java new file mode 100644 index 0000000..0e8427c --- /dev/null +++ b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionWithInnerSectionTest.java @@ -0,0 +1,53 @@ +package io.quarkiverse.qute.web.markdown.test; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.qute.Template; +import io.quarkus.test.QuarkusUnitTest; + +public class QuteMarkdownSectionWithInnerSectionTest { + + @RegisterExtension + static final QuarkusUnitTest quarkusApp = new QuarkusUnitTest() + .withApplicationRoot( + app -> app.addAsResource(new StringAsset( + """ +

Quarkus and Qute

+ {#md} + # Qute and Roq + Here is a list: + {#for item in items} + - an {item} as a list item + {/for} + {/md} + """), + "templates/foo.txt")); + + @Inject + Template foo; + + @Test + void testJsonObjectValueResolver() { + String result = foo.data("items", List.of("apple", "banana", "cherry")) + .render(); + + Assertions.assertEquals(""" +

Quarkus and Qute

+

Qute and Roq

+

Here is a list:

+ + """, result); + + } +} diff --git a/markdown/pom.xml b/markdown/pom.xml new file mode 100644 index 0000000..3c5142a --- /dev/null +++ b/markdown/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + + io.quarkiverse.qute.web + quarkus-qute-web-parent + 999-SNAPSHOT + ../pom.xml + + quarkus-qute-web-markdown-parent + 999-SNAPSHOT + pom + Quarkus Qute Web - Markdown - Parent + + + deployment + runtime + + diff --git a/markdown/runtime/pom.xml b/markdown/runtime/pom.xml new file mode 100644 index 0000000..c956518 --- /dev/null +++ b/markdown/runtime/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + + io.quarkiverse.qute.web + quarkus-qute-web-markdown-parent + 999-SNAPSHOT + + quarkus-qute-web-markdown + Quarkus Qute - Markdown - Runtime + + + + io.quarkus + quarkus-qute + + + org.commonmark + commonmark + ${commonmark.version} + + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.version} + + + compile + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/MarkdownSectionHelperFactory.java b/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/MarkdownSectionHelperFactory.java new file mode 100644 index 0000000..1eb7b51 --- /dev/null +++ b/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/MarkdownSectionHelperFactory.java @@ -0,0 +1,40 @@ +package io.quarkiverse.qute.web.markdown.runtime; + +import java.util.List; +import java.util.concurrent.CompletionStage; + +import io.quarkiverse.qute.web.markdown.runtime.commonmark.CommonMarkConverter; +import io.quarkus.qute.CompletedStage; +import io.quarkus.qute.EngineConfiguration; +import io.quarkus.qute.ResultNode; +import io.quarkus.qute.SectionHelper; +import io.quarkus.qute.SectionHelperFactory; +import io.quarkus.qute.SingleResultNode; + +@EngineConfiguration +public class MarkdownSectionHelperFactory implements SectionHelperFactory { + private static final List MARKDOWN_SECTIONS = List.of("markdown", "md"); + + @Override + public List getDefaultAliases() { + return MARKDOWN_SECTIONS; + } + + @Override + public MarkdownSectionHelper initialize(SectionInitContext context) { + return new MarkdownSectionHelper(); + } + + static class MarkdownSectionHelper implements SectionHelper { + private final CommonMarkConverter converter = new CommonMarkConverter(); + + @Override + public CompletionStage resolve(SectionResolutionContext context) { + return context.execute().thenCompose(rn -> { + StringBuilder sb = new StringBuilder(); + rn.process(sb::append); + return CompletedStage.of(new SingleResultNode(converter.apply(sb.toString()))); + }); + } + } +} diff --git a/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/commonmark/CommonMarkConverter.java b/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/commonmark/CommonMarkConverter.java new file mode 100644 index 0000000..7a20653 --- /dev/null +++ b/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/commonmark/CommonMarkConverter.java @@ -0,0 +1,16 @@ +package io.quarkiverse.qute.web.markdown.runtime.commonmark; + +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; + +public class CommonMarkConverter { + + private final Parser parser = Parser.builder().build(); + private final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build(); + + public String apply(String markdown) { + Node node = parser.parse(markdown); + return htmlRenderer.render(node); + } +} diff --git a/markdown/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/markdown/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000..6382132 --- /dev/null +++ b/markdown/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,15 @@ +name: Qute Web Markdown +#description: Do something useful. +metadata: + keywords: + - qute-web-markdown + - roq + - qute + - website + - templates + - markdown + - md + guide: https://quarkiverse.github.io/quarkiverse-docs/quarkus-qute-web/dev/index.html # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension + categories: + - "miscellaneous" + status: "preview" diff --git a/pom.xml b/pom.xml index c5673a5..79e5e84 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ deployment runtime docs + markdown scm:git:git@github.com:quarkiverse/quarkus-qute-web.git @@ -28,6 +29,7 @@ UTF-8 UTF-8 3.6.9 + 0.22.0