diff --git a/initializr-generator-spring/pom.xml b/initializr-generator-spring/pom.xml
index d129f251..fc7d0807 100644
--- a/initializr-generator-spring/pom.xml
+++ b/initializr-generator-spring/pom.xml
@@ -19,6 +19,11 @@
initializr-metadata
+
+ com.samskivert
+ jmustache
+
+
io.spring.initializrinitializr-generator
diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/GettingStartedSection.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/GettingStartedSection.java
new file mode 100644
index 00000000..e5ed5e53
--- /dev/null
+++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/GettingStartedSection.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
+import io.spring.initializr.generator.io.text.BulletedSection;
+import io.spring.initializr.generator.io.text.Section;
+
+/**
+ * Section that provides links and other important references to get started.
+ *
+ * @author Madhura Bhave
+ * @author Stephane Nicoll
+ */
+public final class GettingStartedSection extends PreDefinedSection {
+
+ private final BulletedSection referenceDocs;
+
+ private final BulletedSection guides;
+
+ private final BulletedSection additionalLinks;
+
+ GettingStartedSection(MustacheTemplateRenderer templateRenderer) {
+ super("Getting Started");
+ this.referenceDocs = new BulletedSection<>(templateRenderer,
+ "documentation/reference-documentation");
+ this.guides = new BulletedSection<>(templateRenderer, "documentation/guides");
+ this.additionalLinks = new BulletedSection<>(templateRenderer,
+ "documentation/additional-links");
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return referenceDocs().isEmpty() && guides().isEmpty()
+ && additionalLinks().isEmpty() && super.isEmpty();
+ }
+
+ @Override
+ protected List resolveSubSections(List sections) {
+ List allSections = new ArrayList<>();
+ allSections.add(this.referenceDocs);
+ allSections.add(this.guides);
+ allSections.add(this.additionalLinks);
+ allSections.addAll(sections);
+ return allSections;
+ }
+
+ public GettingStartedSection addReferenceDocLink(String href, String description) {
+ this.referenceDocs.addItem(new Link(href, description));
+ return this;
+ }
+
+ public BulletedSection referenceDocs() {
+ return this.referenceDocs;
+ }
+
+ public GettingStartedSection addGuideLink(String href, String description) {
+ this.guides.addItem(new Link(href, description));
+ return this;
+ }
+
+ public BulletedSection guides() {
+ return this.guides;
+ }
+
+ public GettingStartedSection addAdditionalLink(String href, String description) {
+ this.additionalLinks.addItem(new Link(href, description));
+ return this;
+ }
+
+ public BulletedSection additionalLinks() {
+ return this.additionalLinks;
+ }
+
+ /**
+ * Internal representation of a link.
+ */
+ public static class Link {
+
+ private final String href;
+
+ private final String description;
+
+ Link(String href, String description) {
+ this.href = href;
+ this.description = description;
+ }
+
+ public String getHref() {
+ return this.href;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ }
+
+}
diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocument.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocument.java
new file mode 100644
index 00000000..29adb276
--- /dev/null
+++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocument.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
+import io.spring.initializr.generator.io.text.MustacheSection;
+import io.spring.initializr.generator.io.text.Section;
+
+/**
+ * Project's help document intended to give additional references to the users. Contains a
+ * getting started section, additional sections and a next steps section.
+ *
+ * @author Stephane Nicoll
+ * @author Madhura Bhave
+ */
+public class HelpDocument {
+
+ private final MustacheTemplateRenderer templateRenderer;
+
+ private final GettingStartedSection gettingStarted;
+
+ private final PreDefinedSection nextSteps;
+
+ private final LinkedList sections = new LinkedList<>();
+
+ public HelpDocument(MustacheTemplateRenderer templateRenderer) {
+ this.templateRenderer = templateRenderer;
+ this.gettingStarted = new GettingStartedSection(templateRenderer);
+ this.nextSteps = new PreDefinedSection("Next Steps");
+ }
+
+ public GettingStartedSection gettingStarted() {
+ return this.gettingStarted;
+ }
+
+ public PreDefinedSection nextSteps() {
+ return this.nextSteps;
+ }
+
+ public HelpDocument addSection(Section section) {
+ this.sections.add(section);
+ return this;
+ }
+
+ /**
+ * Add a section rendered by the specified mustache template and model.
+ * @param templateName the name of the mustache template to render
+ * @param model the model that should be used for the rendering
+ * @return this document
+ */
+ public HelpDocument addSection(String templateName, Map model) {
+ return addSection(
+ new MustacheSection(this.templateRenderer, templateName, model));
+ }
+
+ public List getSections() {
+ return Collections.unmodifiableList(this.sections);
+ }
+
+ public void write(PrintWriter writer) throws IOException {
+ LinkedList allSections = new LinkedList<>(this.sections);
+ allSections.addFirst(this.gettingStarted);
+ allSections.addLast(this.nextSteps);
+ for (Section section : allSections) {
+ section.write(writer);
+ }
+ }
+
+ public boolean isEmpty() {
+ return gettingStarted().isEmpty() && this.sections.isEmpty()
+ && nextSteps().isEmpty();
+ }
+
+}
diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentCustomizer.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentCustomizer.java
new file mode 100644
index 00000000..5969e726
--- /dev/null
+++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentCustomizer.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import org.springframework.core.Ordered;
+
+/**
+ * Callback for customizing a project's {@link HelpDocument}. Invoked with an
+ * {@link Ordered order} of {@code 0} by default, considering overriding
+ * {@link #getOrder()} to customize this behaviour.
+ *
+ * @author Stephane Nicoll
+ */
+@FunctionalInterface
+public interface HelpDocumentCustomizer extends Ordered {
+
+ void customize(HelpDocument document);
+
+ @Override
+ default int getOrder() {
+ return 0;
+ }
+
+}
diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectContributor.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectContributor.java
new file mode 100644
index 00000000..57cd1de8
--- /dev/null
+++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectContributor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import io.spring.initializr.generator.project.contributor.ProjectContributor;
+
+/**
+ * {@link ProjectContributor} for the project's {@code HELP.md} file.
+ *
+ * @author Stephane Nicoll
+ * @author Madhura Bhave
+ */
+public class HelpDocumentProjectContributor implements ProjectContributor {
+
+ private final HelpDocument helpDocument;
+
+ public HelpDocumentProjectContributor(HelpDocument helpDocument) {
+ this.helpDocument = helpDocument;
+ }
+
+ @Override
+ public void contribute(Path projectRoot) throws IOException {
+ if (this.helpDocument.isEmpty()) {
+ return;
+ }
+ Path file = Files.createFile(projectRoot.resolve("HELP.md"));
+ try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(file))) {
+ this.helpDocument.write(writer);
+ }
+ }
+
+}
diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectGenerationConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectGenerationConfiguration.java
new file mode 100644
index 00000000..4d8fc1b8
--- /dev/null
+++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectGenerationConfiguration.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
+import io.spring.initializr.generator.project.ProjectGenerationConfiguration;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+
+/**
+ * Configuration for contributions specific to the help documentation of a project.
+ *
+ * @author Stephane Nicoll
+ */
+@ProjectGenerationConfiguration
+@Import(HelpDocumentProjectGenerationDefaultContributorsConfiguration.class)
+public class HelpDocumentProjectGenerationConfiguration {
+
+ @Bean
+ public HelpDocumentProjectContributor helpDocumentProjectContributor(
+ MustacheTemplateRenderer templateRenderer,
+ ObjectProvider helpDocumentCustomizers) {
+ HelpDocument helpDocument = new HelpDocument(templateRenderer);
+ helpDocumentCustomizers.orderedStream()
+ .forEach((customizer) -> customizer.customize(helpDocument));
+ return new HelpDocumentProjectContributor(helpDocument);
+ }
+
+}
diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectGenerationDefaultContributorsConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectGenerationDefaultContributorsConfiguration.java
new file mode 100644
index 00000000..d191e948
--- /dev/null
+++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectGenerationDefaultContributorsConfiguration.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import io.spring.initializr.generator.project.ResolvedProjectDescription;
+import io.spring.initializr.metadata.InitializrMetadata;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * Default {@link HelpDocument} contributors.
+ *
+ * @author Stephane Nicoll
+ */
+@Configuration
+public class HelpDocumentProjectGenerationDefaultContributorsConfiguration {
+
+ @Bean
+ public RequestedDependenciesHelpDocumentCustomizer dependenciesHelpDocumentCustomizer(
+ ResolvedProjectDescription description, InitializrMetadata metadata) {
+ return new RequestedDependenciesHelpDocumentCustomizer(description, metadata);
+ }
+
+}
diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/PreDefinedSection.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/PreDefinedSection.java
new file mode 100644
index 00000000..73193e33
--- /dev/null
+++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/PreDefinedSection.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.spring.initializr.generator.io.text.Section;
+
+/**
+ * Section that is pre-defined and always present in the document. You can only add
+ * additional sections to pre-defined sections.
+ *
+ * @author Madhura Bhave
+ */
+public class PreDefinedSection implements Section {
+
+ private final String title;
+
+ private final List subSections = new ArrayList<>();
+
+ public PreDefinedSection(String title) {
+ this.title = title;
+ }
+
+ public PreDefinedSection addSection(Section section) {
+ this.subSections.add(section);
+ return this;
+ }
+
+ @Override
+ public void write(PrintWriter writer) throws IOException {
+ if (!isEmpty()) {
+ writer.println("# " + this.title);
+ writer.println("");
+ for (Section section : resolveSubSections(this.subSections)) {
+ section.write(writer);
+ }
+ }
+ }
+
+ public boolean isEmpty() {
+ return this.subSections.isEmpty();
+ }
+
+ /**
+ * Resolve the sections to render based on the current registered sections.
+ * @param sections the registered sections
+ * @return the sections to render
+ */
+ protected List resolveSubSections(List sections) {
+ return sections;
+ }
+
+}
diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/RequestedDependenciesHelpDocumentCustomizer.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/RequestedDependenciesHelpDocumentCustomizer.java
new file mode 100644
index 00000000..fcb05553
--- /dev/null
+++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/RequestedDependenciesHelpDocumentCustomizer.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import io.spring.initializr.generator.project.ResolvedProjectDescription;
+import io.spring.initializr.metadata.Dependency;
+import io.spring.initializr.metadata.InitializrMetadata;
+
+import org.springframework.core.Ordered;
+
+/**
+ * A {@link HelpDocumentCustomizer} that register links for selected dependencies.
+ *
+ * @author Stephane Nicoll
+ */
+public class RequestedDependenciesHelpDocumentCustomizer
+ implements HelpDocumentCustomizer {
+
+ private final ResolvedProjectDescription projectDescription;
+
+ private final InitializrMetadata metadata;
+
+ public RequestedDependenciesHelpDocumentCustomizer(
+ ResolvedProjectDescription projectDescription, InitializrMetadata metadata) {
+ this.projectDescription = projectDescription;
+ this.metadata = metadata;
+ }
+
+ @Override
+ public void customize(HelpDocument document) {
+ this.projectDescription.getRequestedDependencies().forEach((id, dependency) -> {
+ Dependency dependencyMetadata = this.metadata.getDependencies().get(id);
+ if (dependencyMetadata != null) {
+ handleDependency(document, dependencyMetadata);
+ }
+ });
+ }
+
+ @Override
+ public int getOrder() {
+ return Ordered.LOWEST_PRECEDENCE;
+ }
+
+ private void handleDependency(HelpDocument document, Dependency dependency) {
+ GettingStartedSection gettingStartedSection = document.gettingStarted();
+ dependency.getLinks().forEach((link) -> {
+ if (link.getDescription() != null && link.getRel() != null) {
+ if ("reference".equals(link.getRel())) {
+ gettingStartedSection.addReferenceDocLink(link.getHref(),
+ link.getDescription());
+ }
+ else if ("guide".equals(link.getRel())) {
+ gettingStartedSection.addGuideLink(link.getHref(),
+ link.getDescription());
+ }
+ else {
+ gettingStartedSection.addAdditionalLink(link.getHref(),
+ link.getDescription());
+ }
+ }
+ });
+ }
+
+}
diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/package-info.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/package-info.java
new file mode 100644
index 00000000..8df4a33c
--- /dev/null
+++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/documentation/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Documentation contributors. Generate a {@code HELP.md} at the root of the project with
+ * additional information based on requested dependencies.
+ */
+package io.spring.initializr.generator.spring.documentation;
diff --git a/initializr-generator-spring/src/main/resources/META-INF/spring.factories b/initializr-generator-spring/src/main/resources/META-INF/spring.factories
index 9abd45d8..3408105c 100644
--- a/initializr-generator-spring/src/main/resources/META-INF/spring.factories
+++ b/initializr-generator-spring/src/main/resources/META-INF/spring.factories
@@ -7,4 +7,5 @@ io.spring.initializr.generator.spring.code.groovy.GroovyProjectGenerationConfigu
io.spring.initializr.generator.spring.code.java.JavaProjectGenerationConfiguration,\
io.spring.initializr.generator.spring.code.kotlin.KotlinProjectGenerationConfiguration,\
io.spring.initializr.generator.spring.configuration.ApplicationConfigurationProjectGenerationConfiguration,\
+io.spring.initializr.generator.spring.documentation.HelpDocumentProjectGenerationConfiguration,\
io.spring.initializr.generator.spring.scm.git.GitProjectGenerationConfiguration
\ No newline at end of file
diff --git a/initializr-generator-spring/src/main/resources/templates/documentation/additional-links.mustache b/initializr-generator-spring/src/main/resources/templates/documentation/additional-links.mustache
new file mode 100644
index 00000000..4ff40a6d
--- /dev/null
+++ b/initializr-generator-spring/src/main/resources/templates/documentation/additional-links.mustache
@@ -0,0 +1,6 @@
+### Additional Links
+These additional references should also help you:
+
+{{#items}}
+* [{{description}}]({{href}})
+{{/items}}
\ No newline at end of file
diff --git a/initializr-generator-spring/src/main/resources/templates/documentation/guides.mustache b/initializr-generator-spring/src/main/resources/templates/documentation/guides.mustache
new file mode 100644
index 00000000..c5efe86d
--- /dev/null
+++ b/initializr-generator-spring/src/main/resources/templates/documentation/guides.mustache
@@ -0,0 +1,6 @@
+### Guides
+The following guides illustrates how to use certain features concretely:
+
+{{#items}}
+* [{{description}}]({{href}})
+{{/items}}
\ No newline at end of file
diff --git a/initializr-generator-spring/src/main/resources/templates/documentation/reference-documentation.mustache b/initializr-generator-spring/src/main/resources/templates/documentation/reference-documentation.mustache
new file mode 100644
index 00000000..6d6c0352
--- /dev/null
+++ b/initializr-generator-spring/src/main/resources/templates/documentation/reference-documentation.mustache
@@ -0,0 +1,6 @@
+### Reference Documentation
+For further reference, please consider the following sections:
+
+{{#items}}
+* [{{description}}]({{href}})
+{{/items}}
\ No newline at end of file
diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/GettingStartedSectionTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/GettingStartedSectionTests.java
new file mode 100644
index 00000000..3370cc85
--- /dev/null
+++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/GettingStartedSectionTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link GettingStartedSection}.
+ *
+ * @author Stephane Nicoll
+ */
+class GettingStartedSectionTests {
+
+ private final MustacheTemplateRenderer renderer = new MustacheTemplateRenderer("");
+
+ @Test
+ void gettingStartedEmpty() {
+ GettingStartedSection gettingStarted = newGettingStartedSection();
+ assertThat(gettingStarted.isEmpty()).isTrue();
+ }
+
+ @Test
+ void gettingStartedWithGuideLinkIsNotEmpty() {
+ GettingStartedSection gettingStarted = newGettingStartedSection();
+ gettingStarted.addGuideLink("https://example.com", "Test");
+ assertThat(gettingStarted.isEmpty()).isFalse();
+ }
+
+ @Test
+ void gettingStartedWithReferenceDocLinkIsNotEmpty() {
+ GettingStartedSection gettingStarted = newGettingStartedSection();
+ gettingStarted.addReferenceDocLink("https://example.com", "Test");
+ assertThat(gettingStarted.isEmpty()).isFalse();
+ }
+
+ @Test
+ void gettingStartedWithAdditionalLinkIsNotEmpty() {
+ GettingStartedSection gettingStarted = newGettingStartedSection();
+ gettingStarted.addAdditionalLink("https://example.com", "Test");
+ assertThat(gettingStarted.isEmpty()).isFalse();
+ }
+
+ @Test
+ void gettingStartedWithSubSectionIsNotEmpty() {
+ GettingStartedSection gettingStarted = newGettingStartedSection();
+ gettingStarted.addSection((writer) -> writer.println("test"));
+ assertThat(gettingStarted.isEmpty()).isFalse();
+ }
+
+ private GettingStartedSection newGettingStartedSection() {
+ return new GettingStartedSection(this.renderer);
+ }
+
+}
diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectContributorTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectContributorTests.java
new file mode 100644
index 00000000..61577c91
--- /dev/null
+++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectContributorTests.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link HelpDocumentProjectContributor}.
+ *
+ * @author Stephane Nicoll
+ */
+class HelpDocumentProjectContributorTests {
+
+ private Path directory;
+
+ private MustacheTemplateRenderer templateRenderer;
+
+ @BeforeEach
+ void setup(@TempDir Path directory) {
+ this.directory = directory;
+ this.templateRenderer = new MustacheTemplateRenderer("classpath:/templates");
+ }
+
+ @Test
+ void helpDocumentEmptyDoesNotCreateFile() throws IOException {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ assertThat(document.isEmpty()).isTrue();
+ Path projectDir = Files.createTempDirectory(this.directory, "project-");
+ new HelpDocumentProjectContributor(document).contribute(projectDir);
+ Path helpDocument = projectDir.resolve("HELP.md");
+ assertThat(helpDocument).doesNotExist();
+ }
+
+ @Test
+ void helpDocumentWithLinksToGuide() throws IOException {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ document.gettingStarted().addGuideLink("https://test.example.com", "test")
+ .addGuideLink("https://test2.example.com", "test2");
+ List lines = generateDocument(document);
+ assertThat(lines).containsExactly("# Getting Started", "", "### Guides",
+ "The following guides illustrates how to use certain features concretely:",
+ "", "* [test](https://test.example.com)",
+ "* [test2](https://test2.example.com)", "");
+ }
+
+ @Test
+ void helpDocumentWithLinksToReferenceDoc() throws IOException {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ document.gettingStarted().addReferenceDocLink("https://test.example.com", "doc")
+ .addReferenceDocLink("https://test2.example.com", "doc2");
+ List lines = generateDocument(document);
+ assertThat(lines).containsExactly("# Getting Started", "",
+ "### Reference Documentation",
+ "For further reference, please consider the following sections:", "",
+ "* [doc](https://test.example.com)",
+ "* [doc2](https://test2.example.com)", "");
+ }
+
+ @Test
+ void helpDocumentWithLinksToOtherLinks() throws IOException {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ document.gettingStarted().addAdditionalLink("https://test.example.com",
+ "Something");
+ List lines = generateDocument(document);
+ assertThat(lines).containsExactly("# Getting Started", "", "### Additional Links",
+ "These additional references should also help you:", "",
+ "* [Something](https://test.example.com)", "");
+ }
+
+ @Test
+ void helpDocumentWithSimpleSection() throws IOException {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ document.addSection((writer) -> writer
+ .println(String.format("# My test section%n%n * Test")));
+ List lines = generateDocument(document);
+ assertThat(lines).containsExactly("# My test section", "", " * Test");
+ }
+
+ @Test
+ void helpDocumentWithLinksAndSimpleSection() throws IOException {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ document.gettingStarted().addGuideLink("https://test.example.com", "test")
+ .addSection((writer) -> writer
+ .println(String.format("# My test section%n%n * Test")));
+ List lines = generateDocument(document);
+ assertThat(lines).containsExactly("# Getting Started", "", "### Guides",
+ "The following guides illustrates how to use certain features concretely:",
+ "", "* [test](https://test.example.com)", "", "# My test section", "",
+ " * Test");
+ }
+
+ private List generateDocument(HelpDocument document) throws IOException {
+ Path projectDir = Files.createTempDirectory(this.directory, "project-");
+ new HelpDocumentProjectContributor(document).contribute(projectDir);
+ Path helpDocument = projectDir.resolve("HELP.md");
+ assertThat(helpDocument).isRegularFile();
+ return Files.readAllLines(helpDocument);
+ }
+
+}
diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectGenerationConfigurationTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectGenerationConfigurationTests.java
new file mode 100644
index 00000000..72fb929f
--- /dev/null
+++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/HelpDocumentProjectGenerationConfigurationTests.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import java.nio.file.Path;
+
+import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
+import io.spring.initializr.generator.project.ProjectDescription;
+import io.spring.initializr.generator.spring.test.InitializrMetadataTestBuilder;
+import io.spring.initializr.generator.test.project.ProjectAssetTester;
+import io.spring.initializr.metadata.Dependency;
+import io.spring.initializr.metadata.InitializrMetadata;
+import io.spring.initializr.metadata.Link;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link HelpDocumentProjectGenerationConfiguration}.
+ *
+ * @author Stephane Nicoll
+ */
+class HelpDocumentProjectGenerationConfigurationTests {
+
+ private ProjectAssetTester projectTester;
+
+ private InitializrMetadataTestBuilder metadataBuilder = InitializrMetadataTestBuilder
+ .withDefaults();
+
+ @BeforeEach
+ void setup(@TempDir Path directory) {
+ this.projectTester = new ProjectAssetTester()
+ .withConfiguration(HelpDocumentProjectGenerationConfiguration.class)
+ .withBean(MustacheTemplateRenderer.class,
+ () -> new MustacheTemplateRenderer("classpath:/templates"))
+ .withBean(InitializrMetadata.class, () -> this.metadataBuilder.build())
+ .withDirectory(directory);
+ }
+
+ @Test
+ void helpDocumentIsNotContributedWithoutLinks() {
+ assertThat(this.projectTester.generate(new ProjectDescription())
+ .getRelativePathsOfProjectFiles()).isEmpty();
+ }
+
+ @Test
+ void helpDocumentIsContributedWithLinks() {
+ Dependency dependency = Dependency.withId("example", "com.example", "example");
+ dependency.getLinks().add(
+ Link.create("guide", "https://example.com/how-to", "How-to example"));
+ dependency.getLinks().add(Link.create("reference", "https://example.com/doc",
+ "Reference doc example"));
+ this.metadataBuilder.addDependencyGroup("test", dependency);
+ ProjectDescription description = new ProjectDescription();
+ description.addDependency("example", null);
+ assertThat(
+ this.projectTester.generate(description).getRelativePathsOfProjectFiles())
+ .containsOnly("HELP.md");
+ }
+
+}
diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/HelpDocumentTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/HelpDocumentTests.java
new file mode 100644
index 00000000..9d02455a
--- /dev/null
+++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/HelpDocumentTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+/**
+ * Tests for {@link HelpDocument}.
+ *
+ * @author Stephane Nicoll
+ */
+class HelpDocumentTests {
+
+ private final MustacheTemplateRenderer templateRenderer = new MustacheTemplateRenderer(
+ "classpath:/templates");
+
+ @Test
+ void renderEmptyDocumentDoesNotCallWriter() throws IOException {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ PrintWriter out = mock(PrintWriter.class);
+ document.write(out);
+ verifyZeroInteractions(out);
+ }
+
+ @Test
+ void renderSingleSection() {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ document.addSection((writer) -> writer.println("# Test"));
+ String out = write(document);
+ assertThat(out).contains("# Test", "");
+ }
+
+ @Test
+ void renderLinks() {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ document.gettingStarted().addReferenceDocLink("https://example.com/doc", "Doc");
+ document.gettingStarted().addGuideLink("https://example.com/guide-1", "Guide 1");
+ document.gettingStarted().addGuideLink("https://example.com/guide-2", "Guide 2");
+ String out = write(document);
+ assertThat(out).contains("# Getting Started", "", "### Reference Documentation",
+ "For further reference, please consider the following sections:", "",
+ "* [Doc](https://example.com/doc)", "", "### Guides",
+ "The following guides illustrates how to use certain features concretely:",
+ "", "* [Guide 1](https://example.com/guide-1)",
+ "* [Guide 2](https://example.com/guide-2)", "");
+ }
+
+ @Test
+ void renderOnlyAdditionalLink() {
+ HelpDocument document = new HelpDocument(this.templateRenderer);
+ document.gettingStarted().addAdditionalLink("https://example.com/app",
+ "Test App");
+ String out = write(document);
+ assertThat(out).contains("# Getting Started", "", "### Additional Links",
+ "These additional references should also help you:", "",
+ "* [Test App](https://example.com/app)", "");
+ }
+
+ private String write(HelpDocument document) {
+ try {
+ StringWriter out = new StringWriter();
+ document.write(new PrintWriter(out));
+ return out.toString();
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+}
diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/RequestedDependenciesHelpDocumentCustomizerTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/RequestedDependenciesHelpDocumentCustomizerTests.java
new file mode 100644
index 00000000..323b4be0
--- /dev/null
+++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/documentation/RequestedDependenciesHelpDocumentCustomizerTests.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.spring.documentation;
+
+import java.util.List;
+
+import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
+import io.spring.initializr.generator.project.ProjectDescription;
+import io.spring.initializr.generator.project.ResolvedProjectDescription;
+import io.spring.initializr.generator.spring.test.InitializrMetadataTestBuilder;
+import io.spring.initializr.metadata.Dependency;
+import io.spring.initializr.metadata.InitializrMetadata;
+import io.spring.initializr.metadata.Link;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link RequestedDependenciesHelpDocumentCustomizer}.
+ *
+ * @author Stephane Nicoll
+ */
+class RequestedDependenciesHelpDocumentCustomizerTests {
+
+ private final InitializrMetadataTestBuilder metadataBuilder = InitializrMetadataTestBuilder
+ .withDefaults();
+
+ @Test
+ void dependencyLinkWithNoDescriptionIsIgnored() {
+ Dependency dependency = Dependency.withId("example", "com.example", "example");
+ dependency.getLinks().add(Link.create("guide", "https://example.com/how-to"));
+ this.metadataBuilder.addDependencyGroup("test", dependency);
+ HelpDocument document = customizeHelp("example");
+ assertThat(document.gettingStarted().isEmpty()).isTrue();
+ }
+
+ @Test
+ void dependencyWithReferenceDocLink() {
+ Dependency dependency = Dependency.withId("example", "com.example", "example");
+ dependency.getLinks().add(Link.create("reference", "https://example.com/doc",
+ "Reference doc example"));
+ this.metadataBuilder.addDependencyGroup("test", dependency);
+ HelpDocument document = customizeHelp("example");
+ assertThat(document.gettingStarted().isEmpty()).isFalse();
+ List links = document.gettingStarted().referenceDocs()
+ .getItems();
+ assertThat(links).hasSize(1);
+ assertLink(links.get(0), "https://example.com/doc", "Reference doc example");
+ }
+
+ @Test
+ void dependencyWithGuideLink() {
+ Dependency dependency = Dependency.withId("example", "com.example", "example");
+ dependency.getLinks().add(
+ Link.create("guide", "https://example.com/how-to", "How-to example"));
+ this.metadataBuilder.addDependencyGroup("test", dependency);
+ HelpDocument document = customizeHelp("example");
+ assertThat(document.gettingStarted().isEmpty()).isFalse();
+ List links = document.gettingStarted().guides()
+ .getItems();
+ assertThat(links).hasSize(1);
+ assertLink(links.get(0), "https://example.com/how-to", "How-to example");
+ }
+
+ @Test
+ void dependencyWithAdditionalLink() {
+ Dependency dependency = Dependency.withId("example", "com.example", "example");
+ dependency.getLinks()
+ .add(Link.create("something", "https://example.com/test", "Test App"));
+ this.metadataBuilder.addDependencyGroup("test", dependency);
+ HelpDocument document = customizeHelp("example");
+ assertThat(document.gettingStarted().isEmpty()).isFalse();
+ List links = document.gettingStarted()
+ .additionalLinks().getItems();
+ assertThat(links).hasSize(1);
+ assertLink(links.get(0), "https://example.com/test", "Test App");
+ }
+
+ private void assertLink(GettingStartedSection.Link link, String href,
+ String description) {
+ assertThat(link.getHref()).isEqualTo(href);
+ assertThat(link.getDescription()).isEqualTo(description);
+ }
+
+ private HelpDocument customizeHelp(String... requestedDependencies) {
+ ProjectDescription description = new ProjectDescription();
+ for (String requestedDependency : requestedDependencies) {
+ description.addDependency(requestedDependency, null);
+ }
+ InitializrMetadata metadata = this.metadataBuilder.build();
+ HelpDocument document = new HelpDocument(
+ new MustacheTemplateRenderer("classpath:/templates"));
+ new RequestedDependenciesHelpDocumentCustomizer(
+ new ResolvedProjectDescription(description), metadata)
+ .customize(document);
+ return document;
+ }
+
+}
diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/io/text/BulletedSection.java b/initializr-generator/src/main/java/io/spring/initializr/generator/io/text/BulletedSection.java
new file mode 100644
index 00000000..927adcd1
--- /dev/null
+++ b/initializr-generator/src/main/java/io/spring/initializr/generator/io/text/BulletedSection.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.io.text;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import io.spring.initializr.generator.io.template.TemplateRenderer;
+
+/**
+ * {@link Section} for list of items using a {@link TemplateRenderer}.
+ *
+ * @param the type of the item in the bullets
+ * @author Madhura Bhave
+ */
+public class BulletedSection implements Section {
+
+ private final TemplateRenderer templateRenderer;
+
+ private final String templateName;
+
+ private final String itemName;
+
+ private List items = new ArrayList<>();
+
+ public BulletedSection(TemplateRenderer templateRenderer, String templateName) {
+ this(templateRenderer, templateName, "items");
+ }
+
+ public BulletedSection(TemplateRenderer templateRenderer, String templateName,
+ String itemName) {
+ this.templateRenderer = templateRenderer;
+ this.templateName = templateName;
+ this.itemName = itemName;
+ }
+
+ public BulletedSection addItem(T item) {
+ this.items.add(item);
+ return this;
+ }
+
+ public boolean isEmpty() {
+ return this.items.isEmpty();
+ }
+
+ public List getItems() {
+ return Collections.unmodifiableList(this.items);
+ }
+
+ @Override
+ public void write(PrintWriter writer) throws IOException {
+ if (!isEmpty()) {
+ Map model = new HashMap<>();
+ model.put(this.itemName, this.items);
+ writer.println(this.templateRenderer.render(this.templateName, model));
+ }
+ }
+
+}
diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/io/text/MustacheSection.java b/initializr-generator/src/main/java/io/spring/initializr/generator/io/text/MustacheSection.java
new file mode 100644
index 00000000..aaba4458
--- /dev/null
+++ b/initializr-generator/src/main/java/io/spring/initializr/generator/io/text/MustacheSection.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.io.text;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+
+import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
+
+/**
+ * {@link Section} that uses a {@link MustacheTemplateRenderer}. Renders the content with
+ * a newline at the end.
+ *
+ * @author Madhura Bhave
+ */
+public class MustacheSection implements Section {
+
+ private final MustacheTemplateRenderer templateRenderer;
+
+ private final String templateName;
+
+ private final Map model;
+
+ public MustacheSection(MustacheTemplateRenderer templateRenderer, String templateName,
+ Map model) {
+ this.templateRenderer = templateRenderer;
+ this.templateName = templateName;
+ this.model = model;
+ }
+
+ @Override
+ public void write(PrintWriter writer) throws IOException {
+ writer.println(this.templateRenderer.render(this.templateName,
+ resolveModel(this.model)));
+ }
+
+ /**
+ * Resolve the {@code model} prior to render the section.
+ * @param model the current model
+ * @return the model to use to render this section (never null)
+ */
+ protected Map resolveModel(Map model) {
+ return model;
+ }
+
+}
diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/io/text/BulletedSectionTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/io/text/BulletedSectionTests.java
new file mode 100644
index 00000000..20e9fe9a
--- /dev/null
+++ b/initializr-generator/src/test/java/io/spring/initializr/generator/io/text/BulletedSectionTests.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012-2019 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.spring.initializr.generator.io.text;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.Map;
+
+import io.spring.initializr.generator.io.template.TemplateRenderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+/**
+ * Tests for {@link BulletedSection}.
+ *
+ * @author Stephane Nicoll
+ */
+@ExtendWith(MockitoExtension.class)
+class BulletedSectionTests {
+
+ @Mock
+ private TemplateRenderer renderer;
+
+ @Captor
+ private ArgumentCaptor