diff --git a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DirectedDocGen.java b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DirectedDocGen.java index 20f75d9..6fc98dc 100644 --- a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DirectedDocGen.java +++ b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DirectedDocGen.java @@ -5,6 +5,10 @@ package software.amazon.smithy.docgen.core; +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; +import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.codegen.core.directed.CreateContextDirective; import software.amazon.smithy.codegen.core.directed.CreateSymbolProviderDirective; @@ -21,6 +25,8 @@ import software.amazon.smithy.docgen.core.generators.OperationGenerator; import software.amazon.smithy.docgen.core.generators.ResourceGenerator; import software.amazon.smithy.docgen.core.generators.ServiceGenerator; +import software.amazon.smithy.docgen.core.generators.SnippetGenerator; +import software.amazon.smithy.docgen.core.generators.SnippetGenerator.SnippetComparator; import software.amazon.smithy.docgen.core.generators.StructuredShapeGenerator; import software.amazon.smithy.model.node.ExpectationNotMetException; import software.amazon.smithy.model.traits.InputTrait; @@ -33,6 +39,12 @@ @SmithyUnstableApi final class DirectedDocGen implements DirectedCodegen { + private final PluginContext pluginContext; + + DirectedDocGen(PluginContext pluginContext) { + this.pluginContext = pluginContext; + } + @Override public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective directive) { return new DocSymbolProvider(directive.model(), directive.settings()); @@ -40,12 +52,26 @@ public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective directive) { + var contextGenerator = new MockPluginContextGenerator( + pluginContext, directive.settings().snippetGeneratorSettings()); + var classLoader = pluginContext.getPluginClassLoader().orElse(getClass().getClassLoader()); + + List snippetGenerators = new ArrayList<>(); + for (SnippetGenerator generator : ServiceLoader.load(SnippetGenerator.class, classLoader)) { + var pluginContext = contextGenerator.getStubbedContext(directive.model(), generator.name()); + if (generator.configure(pluginContext, directive.service())) { + snippetGenerators.add(generator); + } + } + snippetGenerators = snippetGenerators.stream().sorted(new SnippetComparator()).toList(); + return new DocGenerationContext( - directive.model(), - directive.settings(), - directive.symbolProvider(), - directive.fileManifest(), - directive.integrations() + directive.model(), + directive.settings(), + directive.symbolProvider(), + directive.fileManifest(), + directive.integrations(), + snippetGenerators ); } diff --git a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DocGenerationContext.java b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DocGenerationContext.java index 0d411d2..3c17aa6 100644 --- a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DocGenerationContext.java +++ b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DocGenerationContext.java @@ -13,6 +13,7 @@ import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.codegen.core.WriterDelegator; import software.amazon.smithy.docgen.core.DocSymbolProvider.FileExtensionDecorator; +import software.amazon.smithy.docgen.core.generators.SnippetGenerator; import software.amazon.smithy.docgen.core.writers.DocWriter; import software.amazon.smithy.model.Model; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -30,6 +31,7 @@ public final class DocGenerationContext implements CodegenContext writerDelegator; private final List docIntegrations; private final DocFormat docFormat; + private final List snippetGenerators; /** * Constructor. @@ -39,13 +41,15 @@ public final class DocGenerationContext implements CodegenContext docIntegrations + List docIntegrations, + List snippetGenerators ) { this.model = model; this.docSettings = docSettings; @@ -74,6 +78,7 @@ public DocGenerationContext( this.docFormat = resolvedFormat; this.symbolProvider = symbolProvider; this.writerDelegator = new WriterDelegator<>(fileManifest, symbolProvider, resolvedFormat.writerFactory()); + this.snippetGenerators = snippetGenerators; } @Override @@ -112,4 +117,11 @@ public List integrations() { public DocFormat docFormat() { return this.docFormat; } + + /** + * @return returns the generators used to add snippets to docs. + */ + public List snippetGenerators() { + return snippetGenerators; + } } diff --git a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DocSettings.java b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DocSettings.java index 257c59f..69e7f19 100644 --- a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DocSettings.java +++ b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/DocSettings.java @@ -6,6 +6,7 @@ package software.amazon.smithy.docgen.core; import java.util.Objects; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.utils.SmithyUnstableApi; @@ -16,9 +17,12 @@ * * @param service The shape id of the service to generate documentation for. * @param format The format to generate documentation in. The default is markdown. + * @param snippetGeneratorSettings Settings to pass along to snippet generators. By + * default, the settings for the plugin in the current projection will be used, + * if available. */ @SmithyUnstableApi -public record DocSettings(ShapeId service, String format) { +public record DocSettings(ShapeId service, String format, ObjectNode snippetGeneratorSettings) { /** * Settings for documentation generation. These can be set in the @@ -30,6 +34,7 @@ public record DocSettings(ShapeId service, String format) { public DocSettings { Objects.requireNonNull(service); Objects.requireNonNull(format); + Objects.requireNonNull(snippetGeneratorSettings); } /** @@ -41,7 +46,8 @@ public record DocSettings(ShapeId service, String format) { public static DocSettings fromNode(ObjectNode pluginSettings) { return new DocSettings( pluginSettings.expectStringMember("service").expectShapeId(), - pluginSettings.getStringMemberOrDefault("format", "sphinx-markdown") + pluginSettings.getStringMemberOrDefault("format", "sphinx-markdown"), + pluginSettings.getObjectMember("snippetGeneratorSettings").orElse(Node.objectNode()) ); } } diff --git a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/MockPluginContextGenerator.java b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/MockPluginContextGenerator.java new file mode 100644 index 0000000..de68a17 --- /dev/null +++ b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/MockPluginContextGenerator.java @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.docgen.core; + +import java.util.Optional; +import software.amazon.smithy.build.MockManifest; +import software.amazon.smithy.build.PluginContext; +import software.amazon.smithy.build.model.ProjectionConfig; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.utils.SmithyUnstableApi; + +/** + * Generates mock plugin contexts to pass along to snippet generators. + */ +@SmithyUnstableApi +class MockPluginContextGenerator { + private final PluginContext baseContext; + private final ObjectNode pluginSettings; + + /** + * Constructs a MockPluginContextGenerator. + * + * @param baseContext The context to base plugin context generation on. + * @param pluginSettings Settings specifically given to pass to plugins via the doc generator. + */ + MockPluginContextGenerator(PluginContext baseContext, ObjectNode pluginSettings) { + if (baseContext.getProjection().isEmpty()) { + // PluginContext will NPE if there's no projection config so you have to do it manually + // until that gets fixed. + var builder = PluginContext.builder() + .projection(baseContext.getProjectionName(), ProjectionConfig.builder().build()) + .model(baseContext.getModel()) + .originalModel(baseContext.getOriginalModel().orElse(baseContext.getModel())) + .events(baseContext.getEvents()) + .settings(baseContext.getSettings()) + .fileManifest(baseContext.getFileManifest()) + .sources(baseContext.getSources()); + baseContext.getPluginClassLoader().ifPresent(builder::pluginClassLoader); + baseContext.getOriginalModel().ifPresent(builder::originalModel); + baseContext.getArtifactName().ifPresent(builder::artifactName); + baseContext = builder.build(); + } + this.baseContext = baseContext; + this.pluginSettings = pluginSettings; + } + + /** + * Create a new plugin context with the given model and plugin name. + * + * @param model The version of the model to hand to the plugin. + * @param pluginName The name of the plugin. This is used to search for existing config. + * @return Returns a plugin context with the given model and config for the named plugin. + */ + PluginContext getStubbedContext(Model model, String pluginName) { + ObjectNode settings = pluginSettings.getObjectMember(pluginName) + .or(() -> baseContext.getProjection() + .map(ProjectionConfig::getPlugins) + .flatMap(plugins -> Optional.ofNullable(plugins.get(pluginName)))) + .orElse(Node.objectNode()); + + return baseContext.toBuilder() + .fileManifest(new MockManifest()) + .artifactName(pluginName) + .model(model) + .settings(settings) + .build(); + } +} diff --git a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/SmithyDocPlugin.java b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/SmithyDocPlugin.java index e3c14d6..3410e98 100644 --- a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/SmithyDocPlugin.java +++ b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/SmithyDocPlugin.java @@ -42,7 +42,7 @@ public void execute(PluginContext pluginContext) { CodegenDirector runner = new CodegenDirector<>(); - runner.directedCodegen(new DirectedDocGen()); + runner.directedCodegen(new DirectedDocGen(pluginContext)); runner.integrationClass(DocIntegration.class); runner.fileManifest(pluginContext.getFileManifest()); runner.model(getValidatedModel(pluginContext.getModel()).unwrap()); diff --git a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/ExampleInputGenerator.java b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/ExampleInputGenerator.java new file mode 100644 index 0000000..ccbcf7d --- /dev/null +++ b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/ExampleInputGenerator.java @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.docgen.core.generators; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.ExamplesTrait.Example; + +public class ExampleInputGenerator implements SnippetGenerator { + @Override + public String name() { + return "input"; + } + + @Override + public boolean isWireProtocolGenerator() { + return true; + } + + @Override + public String tabTitle() { + return "Input"; + } + + @Override + public String language() { + return "json"; + } + + @Override + public String generateShapeSnippet(Shape shape, Node value) { + return ""; + } + + @Override + public String generateExampleSnippet(OperationShape operation, Example example) { + return Node.prettyPrintJson(example.getInput()); + } +} diff --git a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/ExampleOutputGenerator.java b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/ExampleOutputGenerator.java new file mode 100644 index 0000000..f2aff4e --- /dev/null +++ b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/ExampleOutputGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.docgen.core.generators; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.ExamplesTrait.Example; + +public class ExampleOutputGenerator implements SnippetGenerator { + @Override + public String name() { + return "output"; + } + + @Override + public String tabTitle() { + return "Output"; + } + + @Override + public String language() { + return "json"; + } + + @Override + public String generateShapeSnippet(Shape shape, Node value) { + return ""; + } + + @Override + public String generateExampleSnippet(OperationShape operation, Example example) { + return Node.prettyPrintJson(example.getOutput().orElse(Node.objectNode())); + } +} diff --git a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/OperationGenerator.java b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/OperationGenerator.java index d616f14..f046472 100644 --- a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/OperationGenerator.java +++ b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/OperationGenerator.java @@ -21,7 +21,6 @@ import software.amazon.smithy.docgen.core.sections.ShapeSubheadingSection; import software.amazon.smithy.docgen.core.writers.DocWriter; import software.amazon.smithy.docgen.core.writers.DocWriter.ListType; -import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.traits.ExamplesTrait; @@ -159,17 +158,11 @@ private void writeExamples( example.getDocumentation().ifPresent(writer::writeCommonMark); writer.openTabGroup(); - // TODO: create example writer interface allow integrations to register them - - // This is just a dummy placehodler tab here to exercise tab creation before - // there's an interface for it. - writer.openCodeTab("Input", "json"); - writer.write(Node.prettyPrintJson(example.getInput())); - writer.closeCodeTab(); - writer.openCodeTab("Output", "json"); - writer.write(Node.prettyPrintJson(example.getOutput().orElse(Node.objectNode()))); - writer.closeCodeTab(); - + for (var snippetGenerator : context.snippetGenerators()) { + writer.openCodeTab(snippetGenerator.tabTitle(), snippetGenerator.language()); + writer.write(snippetGenerator.generateExampleSnippet(operation, example)); + writer.closeCodeTab(); + } writer.closeTabGroup(); writer.closeHeading(); diff --git a/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/SnippetGenerator.java b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/SnippetGenerator.java new file mode 100644 index 0000000..906c1b8 --- /dev/null +++ b/smithy-docgen-core/src/main/java/software/amazon/smithy/docgen/core/generators/SnippetGenerator.java @@ -0,0 +1,103 @@ +/* + * 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.io.Serializable; +import java.util.Comparator; +import software.amazon.smithy.build.PluginContext; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.traits.ExamplesTrait.Example; +import software.amazon.smithy.utils.SmithyUnstableApi; + +/** + * Generates snippets for documentation. + */ +@SmithyUnstableApi +public interface SnippetGenerator { + + /** + * Gets the name to use for the generator. + * + *

If this snippet generator is associated to a code generator, this must match + * the name of that code generator plugin. + * + * @return the generator's name. + */ + default String name() { + return getClass().getCanonicalName(); + } + + /** + * + * @return returns whether the generator generates wire protocol snippets. + */ + default boolean isWireProtocolGenerator() { + return false; + } + + /** + * Configures the snippet generator. + * + *

If the generator can't be configured with the given configuration, or it + * doesn't support the provided service, it should return {@code false} + * + * @param context A synthetic plugin context to configure the generator with. + * @param service The service being generated for. This takes priority over any + * configuration present. + * @return returns whether the generator is configurable. + */ + default boolean configure(PluginContext context, ServiceShape service) { + return true; + } + + /** + * @return returns the title to use in code tabs. + */ + String tabTitle(); + + /** + * @return returns the language used to format code blocks. + */ + String language(); + + /** + * Generates a snippet that instantiates a shape. + * + * @param shape The shape to instantiate. + * @param value The value that the shape should be instantiated with. + * @return returns a string that demonstrates instantiating the shape. + */ + String generateShapeSnippet(Shape shape, Node value); + + /** + * Generates a snippet for a given modeled example. + * + * @param operation The operation whose example is being generated. + * @param example The example to generate. + * @return returns a string representing the example. + */ + String generateExampleSnippet(OperationShape operation, Example example); + + /** + * Sorts snippet generators with wire protocol generators in front, and + * then alphabetically by name. + */ + final class SnippetComparator implements Comparator, Serializable { + + @Override + public int compare(SnippetGenerator sg1, SnippetGenerator sg2) { + if (sg1.isWireProtocolGenerator() && !sg2.isWireProtocolGenerator()) { + return 1; + } else if (!sg1.isWireProtocolGenerator() && sg2.isWireProtocolGenerator()) { + return -1; + } + return String.CASE_INSENSITIVE_ORDER.compare(sg1.name(), sg2.name()); + } + } +} diff --git a/smithy-docgen-core/src/main/resources/META-INF/services/software.amazon.smithy.docgen.core.generators.SnippetGenerator b/smithy-docgen-core/src/main/resources/META-INF/services/software.amazon.smithy.docgen.core.generators.SnippetGenerator new file mode 100644 index 0000000..53bbefa --- /dev/null +++ b/smithy-docgen-core/src/main/resources/META-INF/services/software.amazon.smithy.docgen.core.generators.SnippetGenerator @@ -0,0 +1,7 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 +# + +software.amazon.smithy.docgen.core.generators.ExampleInputGenerator +software.amazon.smithy.docgen.core.generators.ExampleOutputGenerator