Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protocol traits #27

Merged
merged 3 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.docgen.core.generators;

import java.util.List;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import software.amazon.smithy.docgen.core.DocGenerationContext;
import software.amazon.smithy.docgen.core.DocSymbolProvider;
import software.amazon.smithy.docgen.core.sections.BoundOperationSection;
import software.amazon.smithy.docgen.core.sections.BoundOperationsSection;
import software.amazon.smithy.docgen.core.sections.BoundResourceSection;
import software.amazon.smithy.docgen.core.sections.BoundResourcesSection;
import software.amazon.smithy.docgen.core.sections.ProtocolSection;
import software.amazon.smithy.docgen.core.sections.ProtocolsSection;
import software.amazon.smithy.docgen.core.writers.DocWriter;
import software.amazon.smithy.docgen.core.writers.DocWriter.ListType;
import software.amazon.smithy.model.knowledge.ServiceIndex;
import software.amazon.smithy.model.shapes.EntityShape;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ResourceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.utils.CodeInterceptor;
import software.amazon.smithy.utils.CodeSection;
import software.amazon.smithy.utils.SmithyInternalApi;
import software.amazon.smithy.utils.StringUtils;

/**
* Provides common generation methods for services and resources.
*/
@SmithyInternalApi
final class GeneratorUtils {
private GeneratorUtils() {}

static void generateOperationListing(
DocGenerationContext context,
DocWriter writer,
EntityShape shape,
List<OperationShape> operations
) {
writer.pushState(new BoundOperationsSection(context, shape, operations));

if (operations.isEmpty()) {
writer.popState();
return;
}

var parentLinkId = context.symbolProvider().toSymbol(shape)
.expectProperty(DocSymbolProvider.LINK_ID_PROPERTY, String.class);
writer.openHeading("Operations", parentLinkId + "-operations");
writer.openList(ListType.UNORDERED);

for (var operation : operations) {
writer.pushState(new BoundOperationSection(context, shape, operation));
writeListingElement(context, writer, operation);
writer.popState();
}

writer.closeList(ListType.UNORDERED);
writer.closeHeading();
writer.popState();
}

static void generateResourceListing(
DocGenerationContext context,
DocWriter writer,
EntityShape shape,
List<ResourceShape> resources
) {
writer.pushState(new BoundResourcesSection(context, shape, resources));

if (resources.isEmpty()) {
writer.popState();
return;
}

var parentLinkId = context.symbolProvider().toSymbol(shape)
.expectProperty(DocSymbolProvider.LINK_ID_PROPERTY, String.class);
var heading = shape.isServiceShape() ? "Resources" : "Sub-Resources";
writer.openHeading(heading, parentLinkId + "-" + heading.toLowerCase(Locale.ENGLISH));
writer.openList(ListType.UNORDERED);

for (var resource : resources) {
writer.pushState(new BoundResourceSection(context, shape, resource));
writeListingElement(context, writer, resource);
writer.popState();
}

writer.closeList(ListType.UNORDERED);
writer.closeHeading();
writer.popState();
}

private static void writeListingElement(DocGenerationContext context, DocWriter writer, Shape shape) {
writer.openListItem(ListType.UNORDERED);
var symbol = context.symbolProvider().toSymbol(shape);
writer.writeInline("$R: ", symbol).writeShapeDocs(shape, context.model());
writer.closeListItem(ListType.UNORDERED);
}

static void writeProtocolsSection(DocGenerationContext context, DocWriter writer, Shape shape) {
var protocols = ServiceIndex.of(context.model()).getProtocols(context.settings().service()).keySet();
if (protocols.isEmpty()) {
return;
}
writer.pushState(new ProtocolsSection(context, shape));

AtomicReference<String> tabGroupContents = new AtomicReference<>();
var tabGroup = capture(writer, tabGroupWriter -> {
tabGroupWriter.openTabGroup();
tabGroupContents.set(capture(tabGroupWriter, w -> {
for (var protocol : protocols) {
writeProtocolSection(context, w, shape, protocol);
}
}));
tabGroupWriter.closeTabGroup();
});

if (StringUtils.isBlank(tabGroupContents.get())) {
// The extra newline is needed because the section intercepting logic actually adds one
// by virtue of calling write instead of writeInline
writer.unwrite("$L\n", tabGroup);
}

writer.popState();
}

private static void writeProtocolSection(
DocGenerationContext context,
DocWriter writer,
Shape shape,
ShapeId protocol
) {
var protocolSymbol = context.symbolProvider().toSymbol(context.model().expectShape(protocol));

AtomicReference<String> tabContents = new AtomicReference<>();
var tab = capture(writer, tabWriter -> {
tabWriter.openTab(protocolSymbol.getName());
tabContents.set(capture(tabWriter, w2 -> tabWriter.injectSection(
new ProtocolSection(context, shape, protocol))));
tabWriter.closeTab();
});

if (StringUtils.isBlank(tabContents.get())) {
// The extra newline is needed because the section intercepting logic actually adds one
// by virtue of calling write instead of writeInline
writer.unwrite("$L\n", tab);
}
}

/**
* Captures and returns what is written by the given consumer.
*
* @param writer The writer to capture from.
* @param consumer A consumer that writes text to be captured.
* @return Returns what was written by the consumer.
*/
private static String capture(DocWriter writer, Consumer<DocWriter> consumer) {
var recorder = new RecordingInterceptor();
writer.pushState(new CapturingSection()).onSection(recorder);
consumer.accept(writer);
writer.popState();
return recorder.getContents();
}

private record CapturingSection() implements CodeSection {}

/**
* Records what was written to the section previously and writes it back.
*/
private static final class RecordingInterceptor implements CodeInterceptor<CapturingSection, DocWriter> {
private String contents = null;

public String getContents() {
return contents;
}

@Override
public Class<CapturingSection> sectionType() {
return CapturingSection.class;
}

@Override
public void write(DocWriter writer, String previousText, CapturingSection section) {
contents = previousText;
writer.writeWithNoFormatting(previousText);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,21 @@
* <p>The output of this can be customized in a number of ways. To add details to
* or re-write particular sections, register an interceptor with
* {@link software.amazon.smithy.docgen.core.DocIntegration#interceptors}. The following
* sections are guaranteed to be present:
* sections will be present:
*
* <ul>
* <li>{@link MemberSection}: Enables re-writing the documentation for specific members.
*
* <li>{@link ShapeMembersSection}: Enables re-writing or overwriting the entire list
* of members, including changes made in other sections.
*
* <li>{@link software.amazon.smithy.docgen.core.sections.ProtocolSection} Enables adding
* traits that are specific to a particular protocol. This section will only be present if
* there are protocol traits applied to the service. If there are multiple protocol traits,
* this section will appear once per protocol.
*
* <li>{@link software.amazon.smithy.docgen.core.sections.ProtocolsSection} Enables
* modifying the tab group containing all the protocol traits for all the protocols.
* </ul>
*
* <p>To change the intermediate format (e.g. from markdown to restructured text),
Expand Down Expand Up @@ -114,6 +123,7 @@ public void run() {
writer.injectSection(new ShapeSubheadingSection(context, member));
writer.writeShapeDocs(member, context.model());
writer.injectSection(new ShapeDetailsSection(context, member));
GeneratorUtils.writeProtocolsSection(context, writer, member);
writer.closeDefinitionListItem();
writer.popState();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@
* the operation might return. If a synthetic error needs to be applied to an
* operation, it is better to simply add it to the shape with
* {@link software.amazon.smithy.docgen.core.DocIntegration#preprocessModel}.
*
* <li>{@link software.amazon.smithy.docgen.core.sections.ProtocolSection} Enables adding
* traits that are specific to a particular protocol. This section will only be present if
* there are protocol traits applied to the service. If there are multiple protocol traits,
* this section will appear once per protocol.
*
* <li>{@link software.amazon.smithy.docgen.core.sections.ProtocolsSection} Enables
* modifying the tab group containing all the protocol traits for all the protocols.
* </ul>
*
* Additionally, if the operation's input or output shapes have members the following
Expand Down Expand Up @@ -98,6 +106,7 @@ public void accept(GenerateOperationDirective<DocGenerationContext, DocSettings>
writer.injectSection(new ShapeSubheadingSection(context, operation));
writer.writeShapeDocs(operation, directive.model());
writer.injectSection(new ShapeDetailsSection(context, operation));
GeneratorUtils.writeProtocolsSection(context, writer, operation);

new MemberGenerator(context, writer, operation, MemberListingType.INPUT).run();
new MemberGenerator(context, writer, operation, MemberListingType.OUTPUT).run();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,15 @@ public void accept(DocGenerationContext context, ResourceShape resource) {
writer.injectSection(new ShapeSubheadingSection(context, resource));
writer.writeShapeDocs(resource, context.model());
writer.injectSection(new ShapeDetailsSection(context, resource));
GeneratorUtils.writeProtocolsSection(context, writer, resource);

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

var subResources = resource.getResources().stream().sorted()
.map(id -> context.model().expectShape(id, ResourceShape.class))
.toList();
ServiceShapeGeneratorUtils.generateResourceListing(context, writer, resource, subResources);
GeneratorUtils.generateResourceListing(context, writer, resource, subResources);

generateLifecycleDocs(context, writer, resource);

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

writer.closeHeading();
writer.popState();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ public void accept(GenerateServiceDirective<DocGenerationContext, DocSettings> d

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

var operations = topDownIndex.getContainedOperations(service).stream().sorted().toList();
ServiceShapeGeneratorUtils.generateOperationListing(context, writer, service, operations);
GeneratorUtils.generateOperationListing(context, writer, service, operations);

writeAuthSection(context, writer, service);

Expand Down

This file was deleted.

Loading