Skip to content

Commit

Permalink
WIP: snippet generator interface
Browse files Browse the repository at this point in the history
This adds and makes use of a snippet generator interface that lets
other generators provide examples to be included in the docs.

It's unfortunately more ugly than I'd like, notbably with config.
Generators will essentially have to run much of their generator to
be able to provide an accurate example, so we have to give a fake
plugin context that they can use.

The codegen director will need to be updated to make this more
workable.

This is just a proof of concept meant to show how it might work from
the doc generator side.
  • Loading branch information
JordonPhillips committed Dec 5, 2023
1 parent a850ad3 commit 9678934
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -33,19 +39,39 @@
@SmithyUnstableApi
final class DirectedDocGen implements DirectedCodegen<DocGenerationContext, DocSettings, DocIntegration> {

private final PluginContext pluginContext;

DirectedDocGen(PluginContext pluginContext) {
this.pluginContext = pluginContext;
}

@Override
public SymbolProvider createSymbolProvider(CreateSymbolProviderDirective<DocSettings> directive) {
return new DocSymbolProvider(directive.model(), directive.settings());
}

@Override
public DocGenerationContext createContext(CreateContextDirective<DocSettings, DocIntegration> directive) {
var contextGenerator = new MockPluginContextGenerator(
pluginContext, directive.settings().snippetGeneratorSettings());
var classLoader = pluginContext.getPluginClassLoader().orElse(getClass().getClassLoader());

List<SnippetGenerator> 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
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -30,6 +31,7 @@ public final class DocGenerationContext implements CodegenContext<DocSettings, D
private final WriterDelegator<DocWriter> writerDelegator;
private final List<DocIntegration> docIntegrations;
private final DocFormat docFormat;
private final List<SnippetGenerator> snippetGenerators;

/**
* Constructor.
Expand All @@ -39,13 +41,15 @@ public final class DocGenerationContext implements CodegenContext<DocSettings, D
* @param symbolProvider The symbol provider to use to turn shapes into symbols.
* @param fileManifest The file manifest to write to.
* @param docIntegrations A list of integrations to apply during generation.
* @param snippetGenerators A list of snippet generators to add snippets to docs.
*/
public DocGenerationContext(
Model model,
DocSettings docSettings,
SymbolProvider symbolProvider,
FileManifest fileManifest,
List<DocIntegration> docIntegrations
List<DocIntegration> docIntegrations,
List<SnippetGenerator> snippetGenerators
) {
this.model = model;
this.docSettings = docSettings;
Expand Down Expand Up @@ -74,6 +78,7 @@ public DocGenerationContext(
this.docFormat = resolvedFormat;
this.symbolProvider = symbolProvider;
this.writerDelegator = new WriterDelegator<>(fileManifest, symbolProvider, resolvedFormat.writerFactory());
this.snippetGenerators = snippetGenerators;
}

@Override
Expand Down Expand Up @@ -112,4 +117,11 @@ public List<DocIntegration> integrations() {
public DocFormat docFormat() {
return this.docFormat;
}

/**
* @return returns the generators used to add snippets to docs.
*/
public List<SnippetGenerator> snippetGenerators() {
return snippetGenerators;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -30,6 +34,7 @@ public record DocSettings(ShapeId service, String format) {
public DocSettings {
Objects.requireNonNull(service);
Objects.requireNonNull(format);
Objects.requireNonNull(snippetGeneratorSettings);
}

/**
Expand All @@ -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())
);
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public void execute(PluginContext pluginContext) {
CodegenDirector<DocWriter, DocIntegration, DocGenerationContext, DocSettings> 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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -168,17 +167,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();
Expand Down
Loading

0 comments on commit 9678934

Please sign in to comment.