Skip to content

Commit

Permalink
GH-713 - Polishing.
Browse files Browse the repository at this point in the history
  • Loading branch information
odrotbohm committed Jul 8, 2024
1 parent b94d13b commit 570f347
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -369,17 +369,14 @@ public String renderBeanReferences(ApplicationModule module) {
}

public String renderHeadline(int i, String modules) {

return "=".repeat(i) + " " + modules + System.lineSeparator();
}

public String renderPlantUmlInclude(String componentsFilename) {

return "plantuml::" + componentsFilename + "[]" + System.lineSeparator();
}

public String renderGeneralInclude(String componentsFilename) {

return "include::" + componentsFilename + "[]" + System.lineSeparator();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
* API to create documentation for {@link ApplicationModules}.
*
* @author Oliver Drotbohm
* @author Cora Iberkleid
*/
public class Documenter {

Expand Down Expand Up @@ -134,7 +135,7 @@ public Documenter(ApplicationModules modules, String outputFolder) {
.shape(Shape.Component);

Model model = workspace.getModel();
String systemName = modules.getSystemName().orElse("Modulith");
String systemName = getDefaultedSystemName();

SoftwareSystem system = model.addSoftwareSystem(systemName, "");

Expand Down Expand Up @@ -190,13 +191,13 @@ public Documenter writeDocumentation(DiagramOptions options, CanvasOptions canva
}

/**
* Writes aggregating document called 'all-docs.adoc' that includes any existing component diagrams and canvases.
* using {@link DiagramOptions#defaults()} and {@link CanvasOptions#defaults()}.
* Writes aggregating document called {@code all-docs.adoc} that includes any existing component diagrams and
* canvases. using {@link DiagramOptions#defaults()} and {@link CanvasOptions#defaults()}.
*
* @return the current instance, will never be {@literal null}.
* @since 1.2.2
*/
public Documenter writeAggregatingDocument(){

public Documenter writeAggregatingDocument() {
return writeAggregatingDocument(DiagramOptions.defaults(), CanvasOptions.defaults());
}

Expand All @@ -206,8 +207,9 @@ public Documenter writeAggregatingDocument(){
* @param options must not be {@literal null}.
* @param canvasOptions must not be {@literal null}.
* @return the current instance, will never be {@literal null}.
* @since 1.2.2
*/
public Documenter writeAggregatingDocument(DiagramOptions options, CanvasOptions canvasOptions){
public Documenter writeAggregatingDocument(DiagramOptions options, CanvasOptions canvasOptions) {

Assert.notNull(options, "DiagramOptions must not be null!");
Assert.notNull(canvasOptions, "CanvasOptions must not be null!");
Expand All @@ -220,7 +222,9 @@ public Documenter writeAggregatingDocument(DiagramOptions options, CanvasOptions
var componentsDoc = new StringBuilder();

if (outputFolder.contains(componentsFilename)) {
componentsDoc.append(asciidoctor.renderHeadline(2, modules.getSystemName().orElse("Modules")))

componentsDoc
.append(asciidoctor.renderHeadline(2, getDefaultedSystemName()))
.append(asciidoctor.renderPlantUmlInclude(componentsFilename))
.append(System.lineSeparator());
}
Expand All @@ -230,25 +234,20 @@ public Documenter writeAggregatingDocument(DiagramOptions options, CanvasOptions

// Get diagram file name, e.g. module-inventory.puml
var fileNamePattern = options.getTargetFileName().orElse(DEFAULT_MODULE_COMPONENTS_FILE);
Assert.isTrue(fileNamePattern.contains("%s"), () -> String.format(INVALID_FILE_NAME_PATTERN, fileNamePattern));
var filename = String.format(fileNamePattern, it.getName());

// Get canvas file name, e.g. module-inventory.adoc
var filename = fileNamePattern.formatted(it.getName());
var canvasFilename = canvasOptions.getTargetFileName(it.getName());

// Generate output, e.g.:
/*
== Inventory
plantuml::module-inventory.puml[]
include::module-inventory.adoc[]
*/
var content = new StringBuilder();
content.append((outputFolder.contains(filename) ? asciidoctor.renderPlantUmlInclude(filename) : ""))
.append((outputFolder.contains(canvasFilename) ? asciidoctor.renderGeneralInclude(canvasFilename) : ""));

content.append(outputFolder.contains(filename) ? asciidoctor.renderPlantUmlInclude(filename) : "")
.append(outputFolder.contains(canvasFilename) ? asciidoctor.renderGeneralInclude(canvasFilename) : "");

if (!content.isEmpty()) {
content.insert(0, asciidoctor.renderHeadline(2, it.getDisplayName()))

content
.insert(0, asciidoctor.renderHeadline(2, it.getDisplayName()))
.append(System.lineSeparator());
}

return content.toString();

}).collect(Collectors.joining());
Expand All @@ -257,7 +256,8 @@ public Documenter writeAggregatingDocument(DiagramOptions options, CanvasOptions

// Write file to all-docs.adoc
if (!allDocs.isBlank()) {
Path file = recreateFile("all-docs.adoc");

var file = recreateFile("all-docs.adoc");

try (Writer writer = new FileWriter(file.toFile())) {
writer.write(allDocs);
Expand Down Expand Up @@ -351,9 +351,7 @@ public Documenter writeModuleAsPlantUml(ApplicationModule module, DiagramOptions

var fileNamePattern = options.getTargetFileName().orElse(DEFAULT_MODULE_COMPONENTS_FILE);

Assert.isTrue(fileNamePattern.contains("%s"), () -> String.format(INVALID_FILE_NAME_PATTERN, fileNamePattern));

return writeViewAsPlantUml(view, String.format(fileNamePattern, module.getName()), options);
return writeViewAsPlantUml(view, fileNamePattern.formatted(module.getName()), options);
}

/**
Expand Down Expand Up @@ -584,7 +582,7 @@ private String render(ComponentView view, DiagramOptions options) {
private String createPlantUml(DiagramOptions options) {

ComponentView componentView = createComponentView(options);
componentView.setTitle(modules.getSystemName().orElse("Modules"));
componentView.setTitle(getDefaultedSystemName());

addComponentsToView(() -> modules.stream(), componentView, options, it -> {});

Expand Down Expand Up @@ -664,6 +662,10 @@ private static String getDefaultOutputDirectory() {
return (new File("pom.xml").exists() ? "target" : "build").concat("/").concat(DEFAULT_LOCATION);
}

private String getDefaultedSystemName() {
return modules.getSystemName().orElse("Modules");
}

private static record Connection(Element source, Element target) {
public static Connection of(Relationship relationship) {
return new Connection(relationship.getSource(), relationship.getDestination());
Expand Down Expand Up @@ -771,6 +773,9 @@ public DiagramOptions withTargetOnly(Predicate<ApplicationModule> targetOnly) {
* include a {@code %s} placeholder for the module names.
*/
public DiagramOptions withTargetFileName(String targetFileName) {

Assert.isTrue(targetFileName.contains("%s"), () -> INVALID_FILE_NAME_PATTERN.formatted(targetFileName));

return new DiagramOptions(dependencyTypes, dependencyDepth, exclusions, componentFilter, targetOnly,
targetFileName, colorSelector, defaultDisplayName, style, elementsWithoutRelationships);
}
Expand Down Expand Up @@ -1263,12 +1268,12 @@ private static class OutputFolder {

private final String path;

OutputFolder(String path) {
this.path = path;
}
OutputFolder(String path) {
this.path = path;
}

boolean contains(String filename) {
return Files.exists(Paths.get(path, filename));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
import org.springframework.modulith.core.ApplicationModules;
import org.springframework.modulith.core.DependencyType;
import org.springframework.modulith.docs.Documenter.DiagramOptions;
import org.springframework.util.function.ThrowingConsumer;

import com.acme.myproject.Application;

/**
* Unit tests for {@link Documenter}.
*
* @author Oliver Drotbohm
* @author Cora Iberkleid
*/
class DocumenterTest {

Expand Down Expand Up @@ -73,75 +75,68 @@ void testName() {
}

@Test
void customizesOutputLocation() throws IOException {
void customizesOutputLocation() throws Exception {

String customOutputFolder = "build/spring-modulith";
Path path = Paths.get(customOutputFolder);

try {
doWith(path, it -> {

new Documenter(ApplicationModules.of(Application.class), customOutputFolder).writeModuleCanvases();

assertThat(Files.list(path)).isNotEmpty();
assertThat(path).exists();

} finally {

deleteDirectory(path);
}
});
}

@Test // GH-638
void writesAggregatingDocumentOnlyIfOtherDocsExist() throws IOException {

String customOutputFolder = "build/spring-modulith";
Path path = Paths.get(customOutputFolder);
void createsAggregatingDocumentOnlyIfPartialsExist() throws Exception {

Documenter documenter = new Documenter(ApplicationModules.of(Application.class), customOutputFolder);
var customOutputFolder = "build/spring-modulith";
var path = Paths.get(customOutputFolder);
var documenter = new Documenter(ApplicationModules.of(Application.class), customOutputFolder);

try {
doWith(path, it -> {

// all-docs.adoc should be created
documenter.writeDocumentation();

// Count files
long actualFiles;
try (Stream<Path> stream = Files.walk(path)) {
actualFiles = stream.filter(Files::isRegularFile).count();
}
// Expect 2 files per module plus components diagram and all-docs.adoc
long expectedFiles = (documenter.getModules().stream().count() * 2) + 2;
assertThat(actualFiles).isEqualTo(expectedFiles);

Optional<Path> optionalPath = Files.walk(path)
.filter(p -> p.getFileName().toString().equals("all-docs.adoc"))
.findFirst();
assertThat(optionalPath.isPresent());

// Count non-blank lines in all-docs.adoc
long actualLines;
try (Stream<String> lines = Files.lines(optionalPath.get())) {
actualLines = lines.filter(line -> !line.trim().isEmpty())
.count();
}
// Expect 3 lines per module and 2 lines for components
long expectedLines = (documenter.getModules().stream().count() * 3) + 2;
assertThat(actualLines).isEqualTo(expectedLines);
// 2 per module (PlantUML + Canvas) + component overview + aggregating doc
var expectedFiles = documenter.getModules().stream().count() * 2 + 2;

// all-docs.adoc should not be created
deleteDirectoryContents(path);
// 3 per module (headline + PlantUML + Canvas) + component headline + component PlantUML
var expectedLines = documenter.getModules().stream().count() * 3 + 2;

documenter.writeAggregatingDocument();
assertThat(Files.walk(it).filter(Files::isRegularFile).count())
.isEqualTo(expectedFiles);

optionalPath = Files.walk(path)
.filter(p -> p.getFileName().toString().equals("all-docs.adoc"))
.findFirst();
assertThat(optionalPath.isEmpty());
assertThat(path.resolve("all-docs.adoc")).exists().satisfies(doc -> {
assertThat(Files.lines(doc)
.filter(line -> !line.trim().isEmpty())
.count()).isEqualTo(expectedLines);
});
});
}

} finally {
@Test // GH-638
void doesNotCreateAggregatingDocumentIfNoPartialsExist() throws Exception {

deleteDirectory(path);
}
var customOutputFolder = "build/spring-modulith";
var path = Paths.get(customOutputFolder);

doWith(path, it -> {

var documenter = new Documenter(ApplicationModules.of(Application.class), customOutputFolder)
.writeDocumentation();

deleteDirectoryContents(it);

documenter.writeAggregatingDocument();

var aggregatingDoc = path.resolve("all-docs.adoc");

assertThat(aggregatingDoc).doesNotExist();
});
}

private static void deleteDirectoryContents(Path path) throws IOException {
Expand All @@ -161,4 +156,13 @@ private static void deleteDirectory(Path path) throws IOException {
deleteDirectoryContents(path);
Files.deleteIfExists(path);
}

private static void doWith(Path path, ThrowingConsumer<Path> consumer) throws Exception {

try {
consumer.accept(path);
} finally {
deleteDirectory(path);
}
}
}
8 changes: 5 additions & 3 deletions src/docs/antora/modules/ROOT/pages/documentation.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@ Requires the usage of the `spring-boot-configuration-processor` artifact to extr
[[aggregating-document]]
== Generating an Aggregating Document

The aggregating document can be generated by calling `Documenter.writeAggregatingDocument()`:
When using `Documenter.writeDocumentation(…)` an `all-docs.adoc` file will be generated, linking all generated diagrams and Application Module Canvases.
We can manually generate the aggregating document by calling `Documenter.writeAggregatingDocument()`:

.Generating an aggregating document using `Documenter`
[tabs]
Expand All @@ -325,7 +326,7 @@ class DocumentationTests {
void writeDocumentationSnippets() {
new Documenter(modules)
.writeAggregatingDocument();
.writeAggregatingDocument();
}
}
----
Expand All @@ -346,4 +347,5 @@ class DocumentationTests {
----
======

The aggregating document will include any existing application module component diagrams and application module canvases. If there are none, then this method will not produce an output file.
The aggregating document will include any existing application module component diagrams and application module canvases.
If there are none, then this method will not produce an output file.

0 comments on commit 570f347

Please sign in to comment.