Skip to content

Commit 1b446b4

Browse files
Add conditional protocol trait documentation
This adds a section to write protocol traits to. Each protocol will have its own tab so services with multiple protocols can clearly document them separately in one doc site.
1 parent 19794e1 commit 1b446b4

File tree

11 files changed

+299
-101
lines changed

11 files changed

+299
-101
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package software.amazon.smithy.docgen.core.generators;
7+
8+
import java.util.List;
9+
import java.util.Locale;
10+
import java.util.concurrent.atomic.AtomicReference;
11+
import java.util.function.Consumer;
12+
import software.amazon.smithy.docgen.core.DocGenerationContext;
13+
import software.amazon.smithy.docgen.core.DocSymbolProvider;
14+
import software.amazon.smithy.docgen.core.sections.BoundOperationSection;
15+
import software.amazon.smithy.docgen.core.sections.BoundOperationsSection;
16+
import software.amazon.smithy.docgen.core.sections.BoundResourceSection;
17+
import software.amazon.smithy.docgen.core.sections.BoundResourcesSection;
18+
import software.amazon.smithy.docgen.core.sections.ProtocolSection;
19+
import software.amazon.smithy.docgen.core.sections.ProtocolsSection;
20+
import software.amazon.smithy.docgen.core.writers.DocWriter;
21+
import software.amazon.smithy.docgen.core.writers.DocWriter.ListType;
22+
import software.amazon.smithy.model.knowledge.ServiceIndex;
23+
import software.amazon.smithy.model.shapes.EntityShape;
24+
import software.amazon.smithy.model.shapes.OperationShape;
25+
import software.amazon.smithy.model.shapes.ResourceShape;
26+
import software.amazon.smithy.model.shapes.Shape;
27+
import software.amazon.smithy.model.shapes.ShapeId;
28+
import software.amazon.smithy.utils.CodeInterceptor;
29+
import software.amazon.smithy.utils.CodeSection;
30+
import software.amazon.smithy.utils.SmithyInternalApi;
31+
import software.amazon.smithy.utils.StringUtils;
32+
33+
/**
34+
* Provides common generation methods for services and resources.
35+
*/
36+
@SmithyInternalApi
37+
final class GeneratorUtils {
38+
private GeneratorUtils() {}
39+
40+
static void generateOperationListing(
41+
DocGenerationContext context,
42+
DocWriter writer,
43+
EntityShape shape,
44+
List<OperationShape> operations
45+
) {
46+
writer.pushState(new BoundOperationsSection(context, shape, operations));
47+
48+
if (operations.isEmpty()) {
49+
writer.popState();
50+
return;
51+
}
52+
53+
var parentLinkId = context.symbolProvider().toSymbol(shape)
54+
.expectProperty(DocSymbolProvider.LINK_ID_PROPERTY, String.class);
55+
writer.openHeading("Operations", parentLinkId + "-operations");
56+
writer.openList(ListType.UNORDERED);
57+
58+
for (var operation : operations) {
59+
writer.pushState(new BoundOperationSection(context, shape, operation));
60+
writeListingElement(context, writer, operation);
61+
writer.popState();
62+
}
63+
64+
writer.closeList(ListType.UNORDERED);
65+
writer.closeHeading();
66+
writer.popState();
67+
}
68+
69+
static void generateResourceListing(
70+
DocGenerationContext context,
71+
DocWriter writer,
72+
EntityShape shape,
73+
List<ResourceShape> resources
74+
) {
75+
writer.pushState(new BoundResourcesSection(context, shape, resources));
76+
77+
if (resources.isEmpty()) {
78+
writer.popState();
79+
return;
80+
}
81+
82+
var parentLinkId = context.symbolProvider().toSymbol(shape)
83+
.expectProperty(DocSymbolProvider.LINK_ID_PROPERTY, String.class);
84+
var heading = shape.isServiceShape() ? "Resources" : "Sub-Resources";
85+
writer.openHeading(heading, parentLinkId + "-" + heading.toLowerCase(Locale.ENGLISH));
86+
writer.openList(ListType.UNORDERED);
87+
88+
for (var resource : resources) {
89+
writer.pushState(new BoundResourceSection(context, shape, resource));
90+
writeListingElement(context, writer, resource);
91+
writer.popState();
92+
}
93+
94+
writer.closeList(ListType.UNORDERED);
95+
writer.closeHeading();
96+
writer.popState();
97+
}
98+
99+
private static void writeListingElement(DocGenerationContext context, DocWriter writer, Shape shape) {
100+
writer.openListItem(ListType.UNORDERED);
101+
var symbol = context.symbolProvider().toSymbol(shape);
102+
writer.writeInline("$R: ", symbol).writeShapeDocs(shape, context.model());
103+
writer.closeListItem(ListType.UNORDERED);
104+
}
105+
106+
static void writeProtocolsSection(DocGenerationContext context, DocWriter writer, Shape shape) {
107+
var protocols = ServiceIndex.of(context.model()).getProtocols(context.settings().service()).keySet();
108+
if (protocols.isEmpty()) {
109+
return;
110+
}
111+
writer.pushState(new ProtocolsSection(context, shape));
112+
113+
AtomicReference<String> tabGroupContents = new AtomicReference<>();
114+
var tabGroup = capture(writer, tabGroupWriter -> {
115+
tabGroupWriter.openTabGroup();
116+
tabGroupContents.set(capture(tabGroupWriter, w -> {
117+
for (var protocol : protocols) {
118+
writeProtocolSection(context, w, shape, protocol);
119+
}
120+
}));
121+
tabGroupWriter.closeTabGroup();
122+
});
123+
124+
if (StringUtils.isBlank(tabGroupContents.get())) {
125+
// The extra newline is needed because the section intercepting logic actually adds one
126+
// by virtue of calling write instead of writeInline
127+
writer.unwrite("$L\n", tabGroup);
128+
}
129+
130+
writer.popState();
131+
}
132+
133+
private static void writeProtocolSection(
134+
DocGenerationContext context,
135+
DocWriter writer,
136+
Shape shape,
137+
ShapeId protocol
138+
) {
139+
var protocolSymbol = context.symbolProvider().toSymbol(context.model().expectShape(protocol));
140+
141+
AtomicReference<String> tabContents = new AtomicReference<>();
142+
var tab = capture(writer, tabWriter -> {
143+
tabWriter.openTab(protocolSymbol.getName());
144+
tabContents.set(capture(tabWriter, w2 -> tabWriter.injectSection(
145+
new ProtocolSection(context, shape, protocol))));
146+
tabWriter.closeTab();
147+
});
148+
149+
if (StringUtils.isBlank(tabContents.get())) {
150+
// The extra newline is needed because the section intercepting logic actually adds one
151+
// by virtue of calling write instead of writeInline
152+
writer.unwrite("$L\n", tab);
153+
}
154+
}
155+
156+
/**
157+
* Captures and returns what is written by the given consumer.
158+
*
159+
* @param writer The writer to capture from.
160+
* @param consumer A consumer that writes text to be captured.
161+
* @return Returns what was written by the consumer.
162+
*/
163+
private static String capture(DocWriter writer, Consumer<DocWriter> consumer) {
164+
var recorder = new RecordingInterceptor();
165+
writer.pushState(new CapturingSection()).onSection(recorder);
166+
consumer.accept(writer);
167+
writer.popState();
168+
return recorder.getContents();
169+
}
170+
171+
private record CapturingSection() implements CodeSection {}
172+
173+
/**
174+
* Records what was written to the section previously and writes it back.
175+
*/
176+
private static final class RecordingInterceptor implements CodeInterceptor<CapturingSection, DocWriter> {
177+
private String contents = null;
178+
179+
public String getContents() {
180+
return contents;
181+
}
182+
183+
@Override
184+
public Class<CapturingSection> sectionType() {
185+
return CapturingSection.class;
186+
}
187+
188+
@Override
189+
public void write(DocWriter writer, String previousText, CapturingSection section) {
190+
contents = previousText;
191+
writer.writeWithNoFormatting(previousText);
192+
}
193+
}
194+
}

smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/MemberGenerator.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,21 @@
5151
* <p>The output of this can be customized in a number of ways. To add details to
5252
* or re-write particular sections, register an interceptor with
5353
* {@link software.amazon.smithy.docgen.core.DocIntegration#interceptors}. The following
54-
* sections are guaranteed to be present:
54+
* sections will be present:
5555
*
5656
* <ul>
5757
* <li>{@link MemberSection}: Enables re-writing the documentation for specific members.
58+
*
5859
* <li>{@link ShapeMembersSection}: Enables re-writing or overwriting the entire list
5960
* of members, including changes made in other sections.
61+
*
62+
* <li>{@link software.amazon.smithy.docgen.core.sections.ProtocolSection} Enables adding
63+
* traits that are specific to a particular protocol. This section will only be present if
64+
* there are protocol traits applied to the service. If there are multiple protocol traits,
65+
* this section will appear once per protocol.
66+
*
67+
* <li>{@link software.amazon.smithy.docgen.core.sections.ProtocolsSection} Enables
68+
* modifying the tab group containing all the protocol traits for all the protocols.
6069
* </ul>
6170
*
6271
* <p>To change the intermediate format (e.g. from markdown to restructured text),
@@ -114,6 +123,7 @@ public void run() {
114123
writer.injectSection(new ShapeSubheadingSection(context, member));
115124
writer.writeShapeDocs(member, context.model());
116125
writer.injectSection(new ShapeDetailsSection(context, member));
126+
GeneratorUtils.writeProtocolsSection(context, writer, member);
117127
writer.closeDefinitionListItem();
118128
writer.popState();
119129
}

smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/OperationGenerator.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@
5252
* the operation might return. If a synthetic error needs to be applied to an
5353
* operation, it is better to simply add it to the shape with
5454
* {@link software.amazon.smithy.docgen.core.DocIntegration#preprocessModel}.
55+
*
56+
* <li>{@link software.amazon.smithy.docgen.core.sections.ProtocolSection} Enables adding
57+
* traits that are specific to a particular protocol. This section will only be present if
58+
* there are protocol traits applied to the service. If there are multiple protocol traits,
59+
* this section will appear once per protocol.
60+
*
61+
* <li>{@link software.amazon.smithy.docgen.core.sections.ProtocolsSection} Enables
62+
* modifying the tab group containing all the protocol traits for all the protocols.
5563
* </ul>
5664
*
5765
* Additionally, if the operation's input or output shapes have members the following
@@ -98,6 +106,7 @@ public void accept(GenerateOperationDirective<DocGenerationContext, DocSettings>
98106
writer.injectSection(new ShapeSubheadingSection(context, operation));
99107
writer.writeShapeDocs(operation, directive.model());
100108
writer.injectSection(new ShapeDetailsSection(context, operation));
109+
GeneratorUtils.writeProtocolsSection(context, writer, operation);
101110

102111
new MemberGenerator(context, writer, operation, MemberListingType.INPUT).run();
103112
new MemberGenerator(context, writer, operation, MemberListingType.OUTPUT).run();

smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/ResourceGenerator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,15 @@ public void accept(DocGenerationContext context, ResourceShape resource) {
103103
writer.injectSection(new ShapeSubheadingSection(context, resource));
104104
writer.writeShapeDocs(resource, context.model());
105105
writer.injectSection(new ShapeDetailsSection(context, resource));
106+
GeneratorUtils.writeProtocolsSection(context, writer, resource);
106107

107108
new MemberGenerator(context, writer, resource, MemberListingType.RESOURCE_IDENTIFIERS).run();
108109
new MemberGenerator(context, writer, resource, MemberListingType.RESOURCE_PROPERTIES).run();
109110

110111
var subResources = resource.getResources().stream().sorted()
111112
.map(id -> context.model().expectShape(id, ResourceShape.class))
112113
.toList();
113-
ServiceShapeGeneratorUtils.generateResourceListing(context, writer, resource, subResources);
114+
GeneratorUtils.generateResourceListing(context, writer, resource, subResources);
114115

115116
generateLifecycleDocs(context, writer, resource);
116117

@@ -119,7 +120,7 @@ public void accept(DocGenerationContext context, ResourceShape resource) {
119120
var operations = operationIds.stream().sorted()
120121
.map(id -> context.model().expectShape(id, OperationShape.class))
121122
.toList();
122-
ServiceShapeGeneratorUtils.generateOperationListing(context, writer, resource, operations);
123+
GeneratorUtils.generateOperationListing(context, writer, resource, operations);
123124

124125
writer.closeHeading();
125126
writer.popState();

smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/ServiceGenerator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ public void accept(GenerateServiceDirective<DocGenerationContext, DocSettings> d
9393

9494
// TODO: topographically sort resources
9595
var resources = topDownIndex.getContainedResources(service).stream().sorted().toList();
96-
ServiceShapeGeneratorUtils.generateResourceListing(context, writer, service, resources);
96+
GeneratorUtils.generateResourceListing(context, writer, service, resources);
9797

9898
var operations = topDownIndex.getContainedOperations(service).stream().sorted().toList();
99-
ServiceShapeGeneratorUtils.generateOperationListing(context, writer, service, operations);
99+
GeneratorUtils.generateOperationListing(context, writer, service, operations);
100100

101101
writeAuthSection(context, writer, service);
102102

smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/ServiceShapeGeneratorUtils.java

Lines changed: 0 additions & 96 deletions
This file was deleted.

0 commit comments

Comments
 (0)