diff --git a/SNIPPETS.md b/SNIPPETS.md index 2ce87b5..7055531 100644 --- a/SNIPPETS.md +++ b/SNIPPETS.md @@ -20,6 +20,7 @@ This file was generated by running [AggregateSnippets.java](src/main/java/Aggreg - [**Verify**](#verify) - [**Verify v2**](#verify-v2) - [**Voice**](#voice) + ## Initialize ### Application Auth With Key Contents diff --git a/src/main/java/AggregateSnippets.java b/src/main/java/AggregateSnippets.java index fc7d594..c1d2eff 100644 --- a/src/main/java/AggregateSnippets.java +++ b/src/main/java/AggregateSnippets.java @@ -1,17 +1,101 @@ +/* + * Copyright 2025 Vonage + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.Objects; +import java.util.*; + +public final class AggregateSnippets { -public class AggregateSnippets { - public static void main(String[] args) throws Throwable { + public static void main(String... args) throws Throwable { final var repoRoot = Paths.get("").toAbsolutePath(); final var snippetsSrcRoot = repoRoot.resolve("src/main/java/com/vonage/quickstart"); - final var classFileName = "AggregateSnippets.java"; - var sb = new StringBuilder(1 << 17) + final var aggregator = new AggregateSnippets(snippetsSrcRoot); + aggregator.computeContents(); + aggregator.saveContentsToFile(repoRoot.resolve("SNIPPETS.md")); + if (args.length > 0) { + aggregator.saveLineNumbersToCsv(Paths.get(args[0])); + } + } + + + public record CodeSnippetFileInfo( + Path file, + int mainStartIndex, int mainEndIndex, + int clientStartIndex, int clientEndIndex + ) { } + + private StringBuilder sb; + private final Path snippetsSrcRoot; + private Collection snippetFiles; + + public AggregateSnippets(Path snippetsSrcRoot) { + this.snippetsSrcRoot = Objects.requireNonNull(snippetsSrcRoot); + } + + private void checkComputed() { + if (sb == null || sb.isEmpty()) { + throw new IllegalStateException("Contents not computed yet."); + } + } + + public Collection getLineNumbers() { + checkComputed(); + return snippetFiles; + } + + public String getContents() { + checkComputed(); + return sb.toString(); + } + + public void saveContentsToFile(Path destPath) throws IOException { + Files.writeString(destPath, getContents(), StandardOpenOption.CREATE); + } + + public void saveLineNumbersToCsv(Path destPath) throws IOException { + checkComputed(); + try (var writer = Files.newBufferedWriter(destPath, StandardOpenOption.CREATE)) { + writer.write("File,MainStart,MainEnd,ClientStart,ClientEnd\n"); + for (var metadata : snippetFiles) { + writer.write( + metadata.file.getFileName() + "," + + metadata.mainStartIndex + "," + + metadata.mainEndIndex + "," + + metadata.clientStartIndex + "," + + metadata.clientEndIndex + "\n" + ); + } + } + } + + public void computeContents() throws IOException { + snippetFiles = new ArrayList<>(256); + final String classFileName = getClass().getSimpleName() + ".java"; + sb = new StringBuilder(1 << 17) .append("# Vonage Java SDK Code Snippets\n") .append("Here are all the snippets in this repository.\n") .append("This file was generated by running [").append(classFileName) @@ -19,8 +103,23 @@ public static void main(String[] args) throws Throwable { .append(") from the root of the repository.") .append("\n\n## Contents"); - var allDirs = Files.list(snippetsSrcRoot) - .filter(Files::isDirectory) + var allDirs = getAllDirsSorted(); + + for (var file : allDirs) { + var title = toHeadingTitle(file.getName()); + sb.append("\n- [**").append(title).append("**](#") + .append(title.toLowerCase().replace(' ', '-')).append(")"); + } + sb.append("\n\n"); + + for (var file : allDirs) { + appendSnippetContent(file, 2); + } + } + + private List getAllDirsSorted() { + try (var pathStream = Files.list(snippetsSrcRoot)) { + return pathStream.filter(Files::isDirectory) .map(Path::toFile) .sorted((f1, f2) -> { if (isInitialize(f1)) return -1; @@ -28,28 +127,64 @@ public static void main(String[] args) throws Throwable { return f1.getName().compareToIgnoreCase(f2.getName()); }) .toList(); + } + catch (IOException ex) { + System.err.println("Could not read "+snippetsSrcRoot+": " + ex.getMessage()); + return List.of(); + } + } - for (var file : allDirs) { - var title = toHeadingTitle(file.getName()); - sb.append("\n- [**").append(title).append("**](#") - .append(title.toLowerCase().replace(' ', '-')).append(")"); + private void appendSnippetContent(File path, int level) throws IOException { + var fileName = path.getName(); + if (path.isFile()) { + fileName = fileName.substring(0, fileName.lastIndexOf('.')); + } + if (fileName.trim().length() < 3) return; + + sb.append("#".repeat(level)).append(' ').append(toHeadingTitle(fileName)).append('\n'); + if (path.isDirectory()) { + for (var file : Objects.requireNonNull(path.listFiles())) { + appendSnippetContent(file, level + 1); + } } - sb.append("\n"); + else if (level > 2 && path.getName().endsWith(".java")) { + final String fileContent = Files.readString(path.toPath()), + clientInitEndStr = ".build();\n\n", + clientInitStartStr = "VonageClient client"; - for (var file : allDirs) { - appendSnippetContent(sb, file, 2); + final int clientInitStartIndex = fileContent.indexOf(clientInitStartStr) - 8, + clientInitEndIndex = fileContent.indexOf(clientInitEndStr) + 11, + endIndex = fileContent.lastIndexOf('}', fileContent.lastIndexOf('}') - 1) - 1, + startIndex = Math.min(endIndex, clientInitStartIndex > 0 ? + (isInitialize(path.getParentFile()) ? clientInitStartIndex : clientInitEndIndex) : + fileContent.indexOf('{', fileContent.indexOf('{') + 1) + 2 + ); + + final String nugget = fileContent.substring(startIndex, endIndex) + .stripTrailing().stripIndent().replace("\t", " "); + + sb.append("\n```java\n").append(nugget).append("\n```\n"); + + boolean standalone = clientInitEndIndex < 12; + snippetFiles.add(new CodeSnippetFileInfo(path.toPath(), + lineNumberFromIndex(fileContent, startIndex) + 1, + lineNumberFromIndex(fileContent, endIndex), + standalone ? -1 : lineNumberFromIndex(fileContent, clientInitStartIndex) + 1, + standalone ? -1 : lineNumberFromIndex(fileContent, clientInitEndIndex) + )); } + } - var destPath = repoRoot.resolve("SNIPPETS.md"); - Files.deleteIfExists(destPath); - Files.writeString(destPath, sb.toString(), StandardOpenOption.CREATE_NEW); + private static int lineNumberFromIndex(String content, int index) { + if (index < 0) return -1; + return content.substring(0, index).split("\n").length; } - static boolean isInitialize(File file) { + private static boolean isInitialize(File file) { return file.getName().equals("initialize"); } - static String toHeadingTitle(String title) { + private static String toHeadingTitle(String title) { var acronyms = new String[]{ "jwt", "id", "uuid", "url", "sim", "sms", "rcs", "mms", "psd2", "dlr", "cnam", @@ -73,34 +208,4 @@ static String toHeadingTitle(String title) { } return result; } - - static void appendSnippetContent(StringBuilder contentBuilder, File path, int level) throws IOException { - var fileName = path.getName(); - if (path.isFile()) { - fileName = fileName.substring(0, fileName.lastIndexOf('.')); - } - if (fileName.trim().length() < 3) return; - - contentBuilder.append("#".repeat(level)).append(' ').append(toHeadingTitle(fileName)).append('\n'); - if (path.isDirectory()) { - for (var file : Objects.requireNonNull(path.listFiles())) { - appendSnippetContent(contentBuilder, file, level + 1); - } - } - else if (level > 2 && path.getName().endsWith(".java")) { - final var fileContent = Files.readString(path.toPath()); - final var clientInitEndStr = ".build();\n\n"; - final var clientInitStartStr = "VonageClient client"; - final int endIndex = fileContent.lastIndexOf('}', fileContent.lastIndexOf('}') - 1) - 1; - final int startIndex = Math.min(endIndex, fileContent.contains(clientInitStartStr) ? - (isInitialize(path.getParentFile()) ? fileContent.indexOf(clientInitStartStr) - 8 : - fileContent.indexOf(clientInitEndStr) + clientInitEndStr.length()) : - fileContent.indexOf('{', fileContent.indexOf('{') + 1) + 2 - ); - - final var nugget = fileContent.substring(startIndex, endIndex) - .stripTrailing().stripIndent().replace("\t", " "); - contentBuilder.append("\n```java\n").append(nugget).append("\n```\n"); - } - } }