From 6417a861e5ec7816f3bea431d1a186e0e3596d05 Mon Sep 17 00:00:00 2001
From: Scott Beddall <45376673+scbedd@users.noreply.github.com>
Date: Tue, 12 May 2020 10:31:15 -0700
Subject: [PATCH] Maven CodeSnippet Plugin (#544)
* adding a plugin that can replace embedme and codesnippet usage in the azure-sdk CI builds
---
.gitignore | 1 +
README.md | 7 +-
.../snippet-replacer-maven-plugin/README.md | 133 +++++
.../snippet-replacer-maven-plugin/pom.xml | 149 +++++
.../azuresdk/plugin/SnippetDictionary.java | 33 ++
.../plugin/SnippetOperationResult.java | 15 +
.../azuresdk/plugin/SnippetPluginEntry.java | 35 ++
.../java/azuresdk/plugin/SnippetReplacer.java | 520 ++++++++++++++++++
.../java/azuresdk/plugin/VerifyResult.java | 15 +
.../azuresdk/plugin/SnippetReplacerTests.java | 186 +++++++
.../basic_readme_insertion_after.txt | 23 +
.../basic_readme_insertion_before.txt | 15 +
.../basic_src_snippet_insertion_after.txt | 86 +++
.../basic_src_snippet_insertion_before.txt | 66 +++
.../basic_src_snippet_parse.txt | 61 ++
.../project-to-test/duplicate_snippet_src.txt | 19 +
.../duplicate_snippet_src_multiple.txt | 9 +
.../project-to-test/empty_snippet_def.txt | 6 +
.../test/resources/project-to-test/pom.xml | 24 +
.../readme_insertion_verification_failure.txt | 20 +
.../src_insertion_verification_failure.txt | 84 +++
21 files changed, 1505 insertions(+), 2 deletions(-)
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/README.md
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/pom.xml
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/main/java/azuresdk/plugin/SnippetDictionary.java
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/main/java/azuresdk/plugin/SnippetOperationResult.java
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/main/java/azuresdk/plugin/SnippetPluginEntry.java
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/main/java/azuresdk/plugin/SnippetReplacer.java
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/main/java/azuresdk/plugin/VerifyResult.java
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/java/azuresdk/plugin/SnippetReplacerTests.java
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_readme_insertion_after.txt
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_readme_insertion_before.txt
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_src_snippet_insertion_after.txt
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_src_snippet_insertion_before.txt
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_src_snippet_parse.txt
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/duplicate_snippet_src.txt
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/duplicate_snippet_src_multiple.txt
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/empty_snippet_def.txt
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/pom.xml
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/readme_insertion_verification_failure.txt
create mode 100644 packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/src_insertion_verification_failure.txt
diff --git a/.gitignore b/.gitignore
index 51a809037..85d90b51a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -290,6 +290,7 @@ PublishScripts/
#*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
+!packages/java-packages/
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
diff --git a/README.md b/README.md
index b8618ba8b..1a900146d 100644
--- a/README.md
+++ b/README.md
@@ -7,9 +7,10 @@ This repository contains useful tools that the Azure SDK team utilizes across th
| Package or Intent | Path | Description | Status |
| ------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| doc-warden | [Readme](packages/python-packages/doc-warden/README.md) | A tool used to enforce readme standards across Azure SDK Repos. | [![Build Status](https://dev.azure.com/azure-sdk/public/_apis/build/status/108?branchName=master)](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=108&branchName=master) |
-| pixel-server | [Readme](/web/pixel-server/README.md) | A tiny ASP.NET Core site used to serve a pixel and record impressions. | Not Yet Enabled |
+| pixel-server | [Readme](web/pixel-server/README.md) | A tiny ASP.NET Core site used to serve a pixel and record impressions. | Not Yet Enabled |
| pixel insertion tool | [Readme](scripts/python/readme_tracking/readme.md) | A tool used to insert the requests for images served by `pixel server`. | Not Yet Enabled |
-| Check Enforcer | [Readme](/tools/check-enforcer/README.md) | Manage GitHub check-runs in a mono-repo. | Not Yet Enabled |
+| Check Enforcer | [Readme](tools/check-enforcer/README.md) | Manage GitHub check-runs in a mono-repo. | Not Yet Enabled |
+| Maven Plugin for Snippets | [Readme](packages/java-packages/snippet-replacer-maven-plugin/README.md) | A Maven plugin that that updates code snippets referenced from javadoc comments. | Not Yet Enabled |
## Contributing
@@ -26,3 +27,5 @@ For more information see the [Code of Conduct FAQ](https://opensource.microsoft.
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
[![Azure DevOps builds](https://img.shields.io/azure-devops/build/azure-sdk/internal/1372?label=eng%2Fcommon%20sync)](https://dev.azure.com/azure-sdk/internal/_build/latest?definitionId=1372&branchName=master)
+
+C:/repo/sdk-tools/packages/java-packages/snippet-replacer-maven-plugin
\ No newline at end of file
diff --git a/packages/java-packages/snippet-replacer-maven-plugin/README.md b/packages/java-packages/snippet-replacer-maven-plugin/README.md
new file mode 100644
index 000000000..9588ee3d3
--- /dev/null
+++ b/packages/java-packages/snippet-replacer-maven-plugin/README.md
@@ -0,0 +1,133 @@
+# Snippet Replacer
+
+`snippet-replacer-maven-plugin` allows java devs to reference actual java code in their javadoc comments. Developers running code written by the Azure team want their documentation to be useful. This plugin helps the Azure SDK team deliver on that promise by ensuring that code samples presented in doc-comments is always _actual running code_.
+
+### How does it work?
+
+First, reference the plugin in your maven project's `pom.xml` file.
+
+```xml
+
Instantiating a synchronous Configuration Client
+ * + * + * + * + *View {@link ConfigurationClientBuilder this} for additional ways to construct the client.
+ * + * @see ConfigurationClientBuilder + */ +@ServiceClient(builder = ConfigurationClientBuilder.class, serviceInterfaces = ConfigurationService.class) +public final class ConfigurationClient { +... +``` +After update runs: +``` +... + *Instantiating a synchronous Configuration Client
+ * + * + *+ * ConfigurationClient configurationClient = new ConfigurationClientBuilder() + * .connectionString(connectionString) + * .buildClient(); + *+ * + * + *
View {@link ConfigurationClientBuilder this} for additional ways to construct the client.
+ * + * @see ConfigurationClientBuilder + */ +@ServiceClient(builder = ConfigurationClientBuilder.class, serviceInterfaces = ConfigurationService.class) +public final class ConfigurationClient { +... +``` + +The referenced code snippet will be embedded (with javadoc appropriate encoding) with the properly spaced code snippet. + +### Example of `verify` + +This mode is intended for use within CI systems. It will throw an error detailing where snippets are in need of updating. Any dev can rebuild your project with `update` mode and commit the result. + +### Functionality against root README.md + +While the plugin is mostly intended for use in Javadoc comments, it also will run update and verify operations against the file ${project.basedir}/README.md. + +Example reference before update: + +```` +```Java com.azure.data.applicationconfig.configurationclient.instantiation +``` +```` + +Example of README.md after update: + +```` +```Java com.azure.data.applicationconfig.configurationclient.instantiation +ConfigurationClient configurationClient = new ConfigurationClientBuilder() + .connectionString(connectionString) + .buildClient(); +``` +```` + +Which Renders: + +```Java com.azure.data.applicationconfig.configurationclient.instantiation +ConfigurationClient configurationClient = new ConfigurationClientBuilder() + .connectionString(connectionString) + .buildClient(); +``` + diff --git a/packages/java-packages/snippet-replacer-maven-plugin/pom.xml b/packages/java-packages/snippet-replacer-maven-plugin/pom.xml new file mode 100644 index 000000000..5ca15e03b --- /dev/null +++ b/packages/java-packages/snippet-replacer-maven-plugin/pom.xml @@ -0,0 +1,149 @@ + + +Snippet Definition
+ * + * A snippet definition is delineated by BEGIN and END comments directly in your java source. + * Example: + *+ * // BEGIN: com.azure.data.applicationconfig.configurationclient.instantiation + * ConfigurationClient configurationClient = new ConfigurationClientBuilder() + * .connectionString(connectionString) + * .buildClient(); + * // END: com.azure.data.applicationconfig.configurationclient.instantiation + *+ * + *
Calling a Snippet
+ * + * From within a javadoc comment, embed an html comment + * /* + * <!-- src_embed com.azure.data.applicationconfig.configurationclient.instantiation --> + * ConfigurationClient configurationClient = new ConfigurationClientBuilder() + * .connectionString(connectionString) + * .buildClient(); + * <!-- end com.azure.data.applicationconfig.configurationclient.instantiation --> + * Other javadoc details perhaps. + * */ + * public void myfunction() + * + * + * After finishing update operations, this function will throw a MojoExecutionException after reporting all snippet + * CALLS that have no DEFINITION. + */ + public void runUpdate(File folderToVerify, Log logger) throws IOException, MojoExecutionException { + List", + "", + 1, + "* ", + false); + + if (opResult.result != null) { + try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { + writer.write(opResult.result.toString()); + } + } + + return opResult.errorList; + } + + public SnippetOperationResult
", "", 1, "* ", false); + } + + public SnippetOperationResult
", "", 1, "* ", false); + + assertTrue(opResult.result != null); + assertEquals(opResult.result.toString(),expectedString); + } + + @Test + public void testReadmeInsertion() + throws Exception { + /* + Ensures html encoding, empty and populated snippets replacement + */ + Path snippetSourceFile = _getPathToResource("../../project-to-test/basic_src_snippet_parse.txt"); + Path codeForReplacement = _getPathToResource("../../project-to-test/basic_readme_insertion_before.txt"); + Path expectedOutCome = _getPathToResource("../../project-to-test/basic_readme_insertion_after.txt"); + SnippetReplacer testReplacer = new SnippetReplacer(); + List
", "", 1, "* ", false); + + assertTrue(opResult.result != null); + assertTrue(opResult.result.size() == 2); + assertTrue(opResult.result.get(0).SnippetWithIssues.equals("com.azure.data.applicationconfig.configurationclient.instantiation")); + assertTrue(opResult.result.get(1).SnippetWithIssues.equals("com.azure.core.http.rest.pagedflux.instantiation")); + } + + @Test + public void emptySnippetWorks() throws Exception { + Path single = _getPathToResource("../../project-to-test/empty_snippet_def.txt"); + + List
", + "", 1, "* ", false); + + assertTrue(opResult.errorList.size() == 3); + } +} diff --git a/packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_readme_insertion_after.txt b/packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_readme_insertion_after.txt new file mode 100644 index 000000000..5d48d2dac --- /dev/null +++ b/packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_readme_insertion_after.txt @@ -0,0 +1,23 @@ +##### Create a Configuration Client + +Once you have the value of the connection string you can create the configuration client: + +```Java com.azure.data.applicationconfig.configurationclient.instantiation +ConfigurationClient configurationClient = new ConfigurationClientBuilder() + .connectionString(connectionString) + .buildClient(); +``` + +or + +``` Java com.azure.core.http.rest.pagedflux.instantiation +// A supplier that fetches the first page of data from source/service +Supplier
Instantiating a synchronous Configuration Client
+ * + * + *+ * ConfigurationClient configurationClient = new ConfigurationClientBuilder() + * .connectionString(connectionString) + * .buildClient(); + *+ * + * + *
View {@link ConfigurationClientBuilder this} for additional ways to construct the client.
+ * + * @see ConfigurationClientBuilder + */ +@ServiceClient(builder = ConfigurationClientBuilder.class, serviceInterfaces = ConfigurationService.class) +public final class ConfigurationClient { + private final ConfigurationAsyncClient client; + + /** + * Creates a ConfigurationClient that sends requests to the configuration service at {@code serviceEndpoint}. Each + * service call goes through the {@code pipeline}. + * + * @param client The {@link ConfigurationAsyncClient} that the client routes its request through. + */ + ConfigurationClient(ConfigurationAsyncClient client) { + this.client = client; + } + + /** + * Adds a configuration value in the service if that key does not exist. The {@code label} is optional. + * + *Code Samples
+ * + *Add a setting with the key "prodDBConnection", label "westUS" and value "db_connection".
+ * + * + *+ * ConfigurationSetting result = configurationClient + * .addConfigurationSetting("prodDBConnection", "westUS", "db_connection"); + * System.out.printf("Key: %s, Label: %s, Value: %s", result.getKey(), result.getLabel(), result.getValue()); + *+ * + * + * @param key The key of the configuration setting to add. + * @param label The label of the configuration setting to create. If {@code null} no label will be used. + * @param value The value associated with this configuration setting key. + * @return The {@link ConfigurationSetting} that was created, or {@code null} if a key collision occurs or the key + * is an invalid value (which will also throw ServiceRequestException described below). + * @throws IllegalArgumentException If {@code key} is {@code null}. + * @throws ResourceModifiedException If a ConfigurationSetting with the same key exists. + * @throws HttpResponseException If {@code key} is an empty string. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public ConfigurationSetting addConfigurationSetting(String key, String label, String value) { + return addConfigurationSettingWithResponse( + new ConfigurationSetting().setKey(key).setLabel(label).setValue(value), Context.NONE).getValue(); + } + + /** + * + *
+ * // A supplier that fetches the first page of data from source/service + * Supplier<Mono<PagedResponse<Integer>>> firstPageRetriever = () -> getFirstPage(); + * + * // A function that fetches subsequent pages of data from source/service given a continuation token + * Function<String, Mono<PagedResponse<Integer>>> nextPageRetriever = + * continuationToken -> getNextPage(continuationToken); + * + * PagedFlux<Integer> pagedFlux = new PagedFlux<>(firstPageRetriever, + * nextPageRetriever); + *+ * + */ + public ConfigurationSetting addConfigurationSetting(String key, String label, String value) { + return addConfigurationSettingWithResponse( + new ConfigurationSetting().setKey(key).setLabel(label).setValue(value), Context.NONE).getValue(); + } +} diff --git a/packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_src_snippet_insertion_before.txt b/packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_src_snippet_insertion_before.txt new file mode 100644 index 000000000..db2887ed9 --- /dev/null +++ b/packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_src_snippet_insertion_before.txt @@ -0,0 +1,66 @@ +import java.time.OffsetDateTime; + +/** + * This class provides a client that contains all the operations for {@link ConfigurationSetting ConfigurationSettings} + * in Azure App Configuration Store. Operations allowed by the client are adding, retrieving, deleting, set read-only + * status ConfigurationSettings, and listing settings or revision of a setting based on a + * {@link SettingSelector filter}. + * + *
Instantiating a synchronous Configuration Client
+ * + * + * Existing content waiting for cleanup. + * + * + *View {@link ConfigurationClientBuilder this} for additional ways to construct the client.
+ * + * @see ConfigurationClientBuilder + */ +@ServiceClient(builder = ConfigurationClientBuilder.class, serviceInterfaces = ConfigurationService.class) +public final class ConfigurationClient { + private final ConfigurationAsyncClient client; + + /** + * Creates a ConfigurationClient that sends requests to the configuration service at {@code serviceEndpoint}. Each + * service call goes through the {@code pipeline}. + * + * @param client The {@link ConfigurationAsyncClient} that the client routes its request through. + */ + ConfigurationClient(ConfigurationAsyncClient client) { + this.client = client; + } + + /** + * Adds a configuration value in the service if that key does not exist. The {@code label} is optional. + * + *Code Samples
+ * + *Add a setting with the key "prodDBConnection", label "westUS" and value "db_connection".
+ * + * + * + * + * @param key The key of the configuration setting to add. + * @param label The label of the configuration setting to create. If {@code null} no label will be used. + * @param value The value associated with this configuration setting key. + * @return The {@link ConfigurationSetting} that was created, or {@code null} if a key collision occurs or the key + * is an invalid value (which will also throw ServiceRequestException described below). + * @throws IllegalArgumentException If {@code key} is {@code null}. + * @throws ResourceModifiedException If a ConfigurationSetting with the same key exists. + * @throws HttpResponseException If {@code key} is an empty string. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public ConfigurationSetting addConfigurationSetting(String key, String label, String value) { + return addConfigurationSettingWithResponse( + new ConfigurationSetting().setKey(key).setLabel(label).setValue(value), Context.NONE).getValue(); + } + + /** + * + * + */ + public ConfigurationSetting addConfigurationSetting(String key, String label, String value) { + return addConfigurationSettingWithResponse( + new ConfigurationSetting().setKey(key).setLabel(label).setValue(value), Context.NONE).getValue(); + } +} diff --git a/packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_src_snippet_parse.txt b/packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_src_snippet_parse.txt new file mode 100644 index 000000000..3baa9040d --- /dev/null +++ b/packages/java-packages/snippet-replacer-maven-plugin/src/test/resources/project-to-test/basic_src_snippet_parse.txt @@ -0,0 +1,61 @@ + +/** + * This class contains code samples for generating javadocs through doclets for {@link ConfigurationClient} + */ +public final class ConfigurationClientJavaDocCodeSnippets { + + private String key1 = "key1"; + private String key2 = "key2"; + private String value1 = "val1"; + private String value2 = "val2"; + + /** + * Generates code sample for creating a {@link ConfigurationClient} + * + * @return An instance of {@link ConfigurationClient} + * @throws IllegalStateException If configuration credentials cannot be created. + */ + public ConfigurationClient createAsyncConfigurationClientWithPipeline() { + + /** + * Generates code sample for creating a {@link ConfigurationClient} + * + * @return An instance of {@link ConfigurationClient} + * @throws IllegalStateException If configuration credentials cannot be created + */ + public ConfigurationClient createSyncConfigurationClient() { + String connectionString = getConnectionString(); + // BEGIN: com.azure.data.applicationconfig.configurationclient.instantiation + ConfigurationClient configurationClient = new ConfigurationClientBuilder() + .connectionString(connectionString) + .buildClient(); + // END: com.azure.data.applicationconfig.configurationclient.instantiation + return configurationClient; + } + + /** + * Generates code sample for using {@link ConfigurationClient#addConfigurationSetting(String, String, String)} + */ + public void addConfigurationSetting() { + ConfigurationClient configurationClient = createSyncConfigurationClient(); + // BEGIN: com.azure.data.appconfiguration.ConfigurationClient.addConfigurationSetting#String-String-String + ConfigurationSetting result = configurationClient + .addConfigurationSetting("prodDBConnection", "westUS", "db_connection"); + System.out.printf("Key: %s, Label: %s, Value: %s", result.getKey(), result.getLabel(), result.getValue()); + // END: com.azure.data.appconfiguration.ConfigurationClient.addConfigurationSetting#String-String-String + } + + public void encodedHtmlWorks(){ + // BEGIN: com.azure.core.http.rest.pagedflux.instantiation + // A supplier that fetches the first page of data from source/service + SupplierInstantiating a synchronous Configuration Client
+ * + * + *+ * ConfigurationClient configurationClient = new ConfigurationClientBuilder() + * .connectionString(connectionString) + * .buildClient() + *+ * + * + *
View {@link ConfigurationClientBuilder this} for additional ways to construct the client.
+ * + * @see ConfigurationClientBuilder + */ +@ServiceClient(builder = ConfigurationClientBuilder.class, serviceInterfaces = ConfigurationService.class) +public final class ConfigurationClient { + private final ConfigurationAsyncClient client; + + /** + * Creates a ConfigurationClient that sends requests to the configuration service at {@code serviceEndpoint}. Each + * service call goes through the {@code pipeline}. + * + * @param client The {@link ConfigurationAsyncClient} that the client routes its request through. + */ + ConfigurationClient(ConfigurationAsyncClient client) { + this.client = client; + } + + /** + * Adds a configuration value in the service if that key does not exist. The {@code label} is optional. + * + *Code Samples
+ * + *Add a setting with the key "prodDBConnection", label "westUS" and value "db_connection".
+ * + * + *+ * ConfigurationSetting result = configurationClient + * .addConfigurationSetting("prodDBConnection", "westUS", "db_connection"); + * System.out.printf("Key: %s, Label: %s, Value: %s", result.getKey(), result.getLabel(), result.getValue()); + *+ * + * + * @param key The key of the configuration setting to add. + * @param label The label of the configuration setting to create. If {@code null} no label will be used. + * @param value The value associated with this configuration setting key. + * @return The {@link ConfigurationSetting} that was created, or {@code null} if a key collision occurs or the key + * is an invalid value (which will also throw ServiceRequestException described below). + * @throws IllegalArgumentException If {@code key} is {@code null}. + * @throws ResourceModifiedException If a ConfigurationSetting with the same key exists. + * @throws HttpResponseException If {@code key} is an empty string. + */ + @ServiceMethod(returns = ReturnType.SINGLE) + public ConfigurationSetting addConfigurationSetting(String key, String label, String value) { + return addConfigurationSettingWithResponse( + new ConfigurationSetting().setKey(key).setLabel(label).setValue(value), Context.NONE).getValue(); + } + + /** + * + *
+ * // A supplier that fetches the first page of data from source/service + * Supplier<Mono<PagedResponse<Integer>>> firstPageRetriever = () -> getFirstPage(); + * + * // A function that fetches subsequent pages of data from source/service given a continuation token + * Function<String, Mono<PagedResponse<Integer>>> nextPageRetriever = + * continuationToken -> getNextPage(continuationToken); + * + *+ * + */ + public ConfigurationSetting addConfigurationSetting(String key, String label, String value) { + return addConfigurationSettingWithResponse( + new ConfigurationSetting().setKey(key).setLabel(label).setValue(value), Context.NONE).getValue(); + } +}