Project documentation infrastructure

This commit adds support for an `HelpDocument` that can be generated
alongside the project. Such document can hold an arbitrary number of
sections with pre-defined sections such as "Getting Started" and "Next
Steps".

A default contributor retrieves the links for requested dependencies
and add them to the document.

Closes gh-353

Co-authored-by: Madhura Bhave <mbhave@pivotal.io>
This commit is contained in:
Stephane Nicoll
2019-02-18 11:33:33 +01:00
committed by Madhura Bhave
parent 2746a3a6e7
commit 924a73310a
26 changed files with 1384 additions and 4 deletions

View File

@@ -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 <T> the type of the item in the bullets
* @author Madhura Bhave
*/
public class BulletedSection<T> implements Section {
private final TemplateRenderer templateRenderer;
private final String templateName;
private final String itemName;
private List<T> 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<T> getItems() {
return Collections.unmodifiableList(this.items);
}
@Override
public void write(PrintWriter writer) throws IOException {
if (!isEmpty()) {
Map<String, Object> model = new HashMap<>();
model.put(this.itemName, this.items);
writer.println(this.templateRenderer.render(this.templateName, model));
}
}
}

View File

@@ -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<String, Object> model;
public MustacheSection(MustacheTemplateRenderer templateRenderer, String templateName,
Map<String, Object> 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<String, Object> resolveModel(Map<String, Object> model) {
return model;
}
}

View File

@@ -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<Map<String, Object>> modelCaptor;
@Test
void bulletedSectionEmpty() {
assertThat(new BulletedSection<String>(this.renderer, "test").isEmpty()).isTrue();
}
@Test
void bulletedSectionEmptyDoesNotInvokeRender() throws IOException {
BulletedSection<String> section = new BulletedSection<>(this.renderer, "test");
PrintWriter writer = mock(PrintWriter.class);
section.write(writer);
verifyNoMoreInteractions(writer, this.renderer);
}
@Test
void bulletedSectionWithItem() {
BulletedSection<String> section = new BulletedSection<>(this.renderer, "test");
section.addItem("test");
assertThat(section.isEmpty()).isFalse();
}
@Test
void bulletedSectionWithDefaultItemName() throws IOException {
given(this.renderer.render(eq("template"), any())).willReturn("output");
BulletedSection<String> section = new BulletedSection<>(this.renderer,
"template");
section.addItem("test");
section.write(new PrintWriter(new StringWriter()));
verify(this.renderer).render(eq("template"), this.modelCaptor.capture());
Map<String, Object> model = this.modelCaptor.getValue();
assertThat(model).containsOnly(entry("items", Collections.singletonList("test")));
}
@Test
void bulletedSectionWithCustomItemName() throws IOException {
given(this.renderer.render(eq("template"), any())).willReturn("output");
BulletedSection<String> section = new BulletedSection<>(this.renderer, "template",
"elements");
section.addItem("test");
section.write(new PrintWriter(new StringWriter()));
verify(this.renderer).render(eq("template"), this.modelCaptor.capture());
Map<String, Object> model = this.modelCaptor.getValue();
assertThat(model)
.containsOnly(entry("elements", Collections.singletonList("test")));
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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 com.samskivert.mustache.MustacheException;
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.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link MustacheSection}.
*
* @author Stephane Nicoll
*/
class MustacheSectionTests {
private final MustacheTemplateRenderer renderer = new MustacheTemplateRenderer(
"classpath:/templates/mustache");
@Test
void renderSection() throws IOException {
MustacheSection section = new MustacheSection(this.renderer, "test",
Collections.singletonMap("key", "hello"));
StringWriter writer = new StringWriter();
section.write(new PrintWriter(writer));
assertThat(writer.toString()).isEqualTo(String.format("hello%n"));
}
@Test
void renderSectionWithMissingKey() {
MustacheSection section = new MustacheSection(this.renderer, "test",
Collections.singletonMap("another", "hello"));
assertThatThrownBy(() -> section.write(new PrintWriter(new StringWriter())))
.isInstanceOf(MustacheException.class).hasMessageContaining("key");
}
@Test
void renderSectionWithCustomModelResolution() throws IOException {
MustacheSection section = new MustacheSection(this.renderer, "test",
Collections.emptyMap()) {
@Override
protected Map<String, Object> resolveModel(Map<String, Object> model) {
return Collections.singletonMap("key", "custom");
}
};
StringWriter writer = new StringWriter();
section.write(new PrintWriter(writer));
assertThat(writer.toString()).isEqualTo(String.format("custom%n"));
}
}

View File

@@ -17,13 +17,14 @@
package io.spring.initializr.generator.test.project;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.io.SimpleIndentStrategy;
import io.spring.initializr.generator.io.template.MustacheTemplateRenderer;
import io.spring.initializr.generator.project.DefaultProjectAssetGenerator;
import io.spring.initializr.generator.project.ProjectAssetGenerator;
import io.spring.initializr.generator.project.ProjectDescription;
@@ -49,8 +50,12 @@ public class ProjectGeneratorTester
}
private static Map<Class<?>, Supplier<?>> defaultBeans() {
return Collections.singletonMap(IndentingWriterFactory.class,
Map<Class<?>, Supplier<?>> beans = new HashMap<>();
beans.put(IndentingWriterFactory.class,
() -> IndentingWriterFactory.create(new SimpleIndentStrategy(" ")));
beans.put(MustacheTemplateRenderer.class,
() -> new MustacheTemplateRenderer("classpath:/templates"));
return beans;
}
@Override