Use bullet list for compose services

Closes gh-1419
This commit is contained in:
Stephane Nicoll
2023-06-01 13:16:25 +02:00
parent 2a9d163de1
commit 58f1b946d4
5 changed files with 23 additions and 265 deletions

View File

@@ -16,12 +16,12 @@
package io.spring.initializr.generator.spring.container.docker.compose; package io.spring.initializr.generator.spring.container.docker.compose;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import io.spring.initializr.generator.container.docker.compose.ComposeFile; import io.spring.initializr.generator.container.docker.compose.ComposeFile;
import io.spring.initializr.generator.container.docker.compose.ComposeService; import io.spring.initializr.generator.container.docker.compose.ComposeService;
import io.spring.initializr.generator.spring.container.docker.compose.Markdown.MarkdownTable;
import io.spring.initializr.generator.spring.documentation.HelpDocument; import io.spring.initializr.generator.spring.documentation.HelpDocument;
import io.spring.initializr.generator.spring.documentation.HelpDocumentCustomizer; import io.spring.initializr.generator.spring.documentation.HelpDocumentCustomizer;
@@ -31,11 +31,11 @@ import io.spring.initializr.generator.spring.documentation.HelpDocumentCustomize
* *
* @author Moritz Halbritter * @author Moritz Halbritter
*/ */
public class DockerComposeHelpDocumentCustomizer implements HelpDocumentCustomizer { public class ComposeHelpDocumentCustomizer implements HelpDocumentCustomizer {
private final ComposeFile composeFile; private final ComposeFile composeFile;
public DockerComposeHelpDocumentCustomizer(ComposeFile composeFile) { public ComposeHelpDocumentCustomizer(ComposeFile composeFile) {
this.composeFile = composeFile; this.composeFile = composeFile;
} }
@@ -43,18 +43,16 @@ public class DockerComposeHelpDocumentCustomizer implements HelpDocumentCustomiz
public void customize(HelpDocument document) { public void customize(HelpDocument document) {
Map<String, Object> model = new HashMap<>(); Map<String, Object> model = new HashMap<>();
if (this.composeFile.services().isEmpty()) { if (this.composeFile.services().isEmpty()) {
model.put("serviceTable", null);
document.getWarnings() document.getWarnings()
.addItem( .addItem(
"No Docker Compose services found. As of now, the application won't start! Please add at least one service to the `compose.yaml` file."); "No Docker Compose services found. As of now, the application won't start! Please add at least one service to the `compose.yaml` file.");
} }
else { else {
MarkdownTable serviceTable = Markdown.table("Service name", "Image", "Tag", "Website"); model.put("services",
this.composeFile.services() this.composeFile.services()
.values() .values()
.forEach((service) -> serviceTable.addRow(service.getName(), Markdown.code(service.getImage()), .sorted(Comparator.comparing(ComposeService::getName))
Markdown.code(service.getImageTag()), Markdown.link("Website", service.getImageWebsite()))); .toList());
model.put("serviceTable", serviceTable.toMarkdown());
} }
document.addSection("documentation/docker-compose", model); document.addSection("documentation/docker-compose", model);
} }

View File

@@ -1,158 +0,0 @@
/*
* Copyright 2012-2023 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
*
* https://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.container.docker.compose;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class for Markdown.
*
* @author Moritz Halbritter
*/
final class Markdown {
private Markdown() {
// Static class
}
/**
* Formats the given string as code.
* @param code the input string
* @return string formatted as code
*/
static String code(String code) {
return "`%s`".formatted(code);
}
/**
* Creates a Markdown link.
* @param text text of the link
* @param url url of the link
* @return the formatted link in Markdown
*/
static String link(String text, String url) {
return "[%s](%s)".formatted(text, url);
}
/**
* Creates a Markdown table.
* @param headerCaptions captions of the header
* @return the Markdown table
*/
static MarkdownTable table(String... headerCaptions) {
return new MarkdownTable(headerCaptions);
}
/**
* A Markdown table.
* <p>
* The formatted table is pretty-printed, all the columns are padded with spaces to
* have a consistent look.
*
* @author Moritz Halbritter
*/
static class MarkdownTable {
private final List<String> headerCaptions;
private final List<List<String>> rows;
/**
* Creates a new table with the given header captions.
* @param headerCaptions the header captions
*/
MarkdownTable(String... headerCaptions) {
this.headerCaptions = List.of(headerCaptions);
this.rows = new ArrayList<>();
}
/**
* Adds a new row with the given cells.
* @param cells the cells to add
* @throws IllegalArgumentException if the cell size doesn't match the number of
* header captions
*/
void addRow(String... cells) {
if (cells.length != this.headerCaptions.size()) {
throw new IllegalArgumentException(
"Expected %d cells, got %d".formatted(this.headerCaptions.size(), cells.length));
}
this.rows.add(List.of(cells));
}
/**
* Formats the whole table as Markdown.
* @return the table formatted as Markdown.
*/
String toMarkdown() {
int[] columnMaxLengths = calculateMaxColumnLengths();
StringBuilder result = new StringBuilder();
writeHeader(result, columnMaxLengths);
writeHeaderSeparator(result, columnMaxLengths);
writeRows(result, columnMaxLengths);
return result.toString();
}
private void writeHeader(StringBuilder result, int[] columnMaxLengths) {
for (int i = 0; i < this.headerCaptions.size(); i++) {
result.append((i > 0) ? " " : "| ")
.append(pad(this.headerCaptions.get(i), columnMaxLengths[i]))
.append(" |");
}
result.append(System.lineSeparator());
}
private void writeHeaderSeparator(StringBuilder result, int[] columnMaxLengths) {
for (int i = 0; i < this.headerCaptions.size(); i++) {
result.append((i > 0) ? " " : "| ").append("-".repeat(columnMaxLengths[i])).append(" |");
}
result.append(System.lineSeparator());
}
private void writeRows(StringBuilder result, int[] columnMaxLengths) {
for (List<String> row : this.rows) {
for (int i = 0; i < row.size(); i++) {
result.append((i > 0) ? " " : "| ").append(pad(row.get(i), columnMaxLengths[i])).append(" |");
}
result.append(System.lineSeparator());
}
}
private int[] calculateMaxColumnLengths() {
int[] columnMaxLengths = new int[this.headerCaptions.size()];
for (int i = 0; i < this.headerCaptions.size(); i++) {
columnMaxLengths[i] = this.headerCaptions.get(i).length();
}
for (List<String> row : this.rows) {
for (int i = 0; i < row.size(); i++) {
String cell = row.get(i);
if (cell.length() > columnMaxLengths[i]) {
columnMaxLengths[i] = cell.length();
}
}
}
return columnMaxLengths;
}
private String pad(String input, int length) {
return input + " ".repeat(length - input.length());
}
}
}

View File

@@ -1,16 +1,15 @@
### Docker Compose support ### Docker Compose support
This project contains a Docker Compose file named `compose.yaml`. This project contains a Docker Compose file named `compose.yaml`.
{{#services.size}}
{{#serviceTable}}
In this file, the following services have been defined: In this file, the following services have been defined:
{{serviceTable}} {{#services}}
* {{name}}: [`{{image}}:{{imageTag}}`]({{imageWebsite}})
{{/services}}
Please review the tags of the used images and set them to the same as you're running in production. Please review the tags of the used images and set them to the same as you're running in production.
{{/serviceTable}} {{/services.size}}{{^services}}
{{^serviceTable}}
However, no services were found. As of now, the application won't start! However, no services were found. As of now, the application won't start!
Please make sure to add at least one service in the `compose.yaml` file. Please make sure to add at least one service in the `compose.yaml` file.
{{/serviceTable}} {{/services}}

View File

@@ -31,24 +31,27 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link DockerComposeHelpDocumentCustomizer}. * Tests for {@link ComposeHelpDocumentCustomizer}.
* *
* @author Moritz Halbritter * @author Moritz Halbritter
* @author Stephane Nicoll
*/ */
class DockerComposeHelpDocumentCustomizerTests { class ComposeHelpDocumentCustomizerTests {
private DockerComposeHelpDocumentCustomizer customizer; private ComposeHelpDocumentCustomizer customizer;
private ComposeFile dockerComposeFile; private ComposeFile dockerComposeFile;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
this.dockerComposeFile = new ComposeFile(); this.dockerComposeFile = new ComposeFile();
this.customizer = new DockerComposeHelpDocumentCustomizer(this.dockerComposeFile); this.customizer = new ComposeHelpDocumentCustomizer(this.dockerComposeFile);
} }
@Test @Test
void addsDockerComposeSection() throws IOException { void addsDockerComposeSection() throws IOException {
this.dockerComposeFile.services()
.add("test2", (service) -> service.imageAndTag("image-2:4.5.6").imageWebsite("https:/example.com/image-2"));
this.dockerComposeFile.services() this.dockerComposeFile.services()
.add("test", (service) -> service.imageAndTag("image-1:1.2.3").imageWebsite("https:/example.com/image-1")); .add("test", (service) -> service.imageAndTag("image-1:1.2.3").imageWebsite("https:/example.com/image-1"));
HelpDocument helpDocument = helpDocument(); HelpDocument helpDocument = helpDocument();
@@ -60,15 +63,11 @@ class DockerComposeHelpDocumentCustomizerTests {
helpDocument.write(new PrintWriter(stringWriter)); helpDocument.write(new PrintWriter(stringWriter));
assertThat(stringWriter.toString()).isEqualToIgnoringNewLines(""" assertThat(stringWriter.toString()).isEqualToIgnoringNewLines("""
### Docker Compose support ### Docker Compose support
This project contains a Docker Compose file named `compose.yaml`. This project contains a Docker Compose file named `compose.yaml`.
In this file, the following services have been defined: In this file, the following services have been defined:
| Service name | Image | Tag | Website | * test: [`image-1:1.2.3`](https:/example.com/image-1)
| ------------ | --------- | ------- | ------------------------------------- | * test2: [`image-2:4.5.6`](https:/example.com/image-2)
| test | `image-1` | `1.2.3` | [Website](https:/example.com/image-1) |
Please review the tags of the used images and set them to the same as you're running in production."""); Please review the tags of the used images and set them to the same as you're running in production.""");
} }
@@ -89,7 +88,6 @@ class DockerComposeHelpDocumentCustomizerTests {
* No Docker Compose services found. As of now, the application won't start! Please add at least one service to the `compose.yaml` file. * No Docker Compose services found. As of now, the application won't start! Please add at least one service to the `compose.yaml` file.
### Docker Compose support ### Docker Compose support
This project contains a Docker Compose file named `compose.yaml`. This project contains a Docker Compose file named `compose.yaml`.
However, no services were found. As of now, the application won't start! However, no services were found. As of now, the application won't start!

View File

@@ -1,79 +0,0 @@
/*
* Copyright 2012-2023 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
*
* https://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.container.docker.compose;
import io.spring.initializr.generator.spring.container.docker.compose.Markdown.MarkdownTable;
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 Markdown}.
*
* @author Moritz Halbritter
*/
class MarkdownTests {
@Test
void shouldFormatCode() {
String code = Markdown.code("c = a + b");
assertThat(code).isEqualTo("`c = a + b`");
}
@Test
void shouldFormatLink() {
String link = Markdown.link("Spring Website", "https://spring.io/");
assertThat(link).isEqualTo("[Spring Website](https://spring.io/)");
}
@Test
void shouldFormatCorrectly() {
MarkdownTable table = new MarkdownTable("a", "b1", "c22", "d333");
table.addRow("0", "1", "2", "3");
table.addRow("4", "5", "6", "7");
String markdown = table.toMarkdown();
assertThat(markdown).isEqualToIgnoringNewLines("""
| a | b1 | c22 | d333 |
| - | -- | --- | ---- |
| 0 | 1 | 2 | 3 |
| 4 | 5 | 6 | 7 |
""");
}
@Test
void rowIsBiggerThanHeading() {
MarkdownTable table = new MarkdownTable("a", "b", "c", "d");
table.addRow("0.0", "1.1", "2.2", "3.3");
table.addRow("4.4", "5.5", "6.6", "7.7");
String markdown = table.toMarkdown();
assertThat(markdown).isEqualToIgnoringNewLines("""
| a | b | c | d |
| --- | --- | --- | --- |
| 0.0 | 1.1 | 2.2 | 3.3 |
| 4.4 | 5.5 | 6.6 | 7.7 |
""");
}
@Test
void throwsIfCellsDifferFromHeader() {
MarkdownTable table = new MarkdownTable("a", "b", "c", "d");
assertThatThrownBy(() -> table.addRow("1")).isInstanceOf(IllegalArgumentException.class)
.hasMessage("Expected 4 cells, got 1");
}
}