forked from loafle/openapi-generator-original
[cli] new 'author template' command (#6441)
* [cli] new 'author template' command This new command allows users to extract templates for authoring (customization) without the complexity of finding and downloading a specific directory for their versioned artifact. Example usage: ``` openapi-generator author template -g java --library webclient ``` This will write all templates with library-specific templates to the './out' directory relative to the current directory. CLI will refer the user to https://openapi-generator.tech/docs/templating after generation * [docs] Usage of author template command * Log warning if author template fails to output requested library
This commit is contained in:
@@ -57,6 +57,11 @@ public class OpenAPIGenerator {
|
||||
GenerateBatch.class
|
||||
);
|
||||
|
||||
builder.withGroup("author")
|
||||
.withDescription("Utilities for authoring generators or customizing templates.")
|
||||
.withDefaultCommand(HelpCommand.class)
|
||||
.withCommands(AuthorTemplate.class);
|
||||
|
||||
try {
|
||||
builder.build().parse(args).run();
|
||||
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
package org.openapitools.codegen.cmd;
|
||||
|
||||
import io.airlift.airline.Command;
|
||||
import io.airlift.airline.Option;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.openapitools.codegen.CodegenConfig;
|
||||
import org.openapitools.codegen.CodegenConfigLoader;
|
||||
import org.openapitools.codegen.CodegenConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.spi.FileSystemProvider;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal", "unused"})
|
||||
@Command(name = "template", description = "Retrieve templates for local modification")
|
||||
public class AuthorTemplate extends OpenApiGeneratorCommand {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AuthorTemplate.class);
|
||||
|
||||
@Option(name = {"-g", "--generator-name"}, title = "generator name",
|
||||
description = "generator to use (see list command for list)",
|
||||
required = true)
|
||||
private String generatorName;
|
||||
|
||||
@Option(name = {"--library"}, title = "library", description = CodegenConstants.LIBRARY_DESC)
|
||||
private String library;
|
||||
|
||||
@Option(name = {"-o", "--output"}, title = "output directory",
|
||||
description = "where to write the template files (defaults to 'out')")
|
||||
private String output = "";
|
||||
|
||||
@Option(name = {"-v", "--verbose"}, description = "verbose mode")
|
||||
private boolean verbose;
|
||||
|
||||
private Pattern pattern = null;
|
||||
|
||||
@Override
|
||||
void execute() {
|
||||
CodegenConfig config = CodegenConfigLoader.forName(generatorName);
|
||||
String templateDirectory = config.templateDir();
|
||||
|
||||
log("Requesting '{}' from embedded resource directory '{}'", generatorName, templateDirectory);
|
||||
|
||||
Path embeddedTemplatePath;
|
||||
try {
|
||||
URI uri = Objects.requireNonNull(this.getClass().getClassLoader().getResource(templateDirectory)).toURI();
|
||||
|
||||
if ("jar".equals(uri.getScheme())) {
|
||||
Optional<FileSystemProvider> provider = FileSystemProvider.installedProviders()
|
||||
.stream()
|
||||
.filter(p -> p.getScheme().equalsIgnoreCase("jar"))
|
||||
.findFirst();
|
||||
|
||||
if (!provider.isPresent()) {
|
||||
throw new ProviderNotFoundException("Unable to load jar file system provider");
|
||||
}
|
||||
|
||||
try {
|
||||
provider.get().getFileSystem(uri);
|
||||
} catch (FileSystemNotFoundException ex) {
|
||||
// File system wasn't loaded, so create it.
|
||||
provider.get().newFileSystem(uri, Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
|
||||
embeddedTemplatePath = Paths.get(uri);
|
||||
|
||||
log("Copying from jar location {}", embeddedTemplatePath.toAbsolutePath().toString());
|
||||
|
||||
File outputDir;
|
||||
if (StringUtils.isNotEmpty(output)) {
|
||||
outputDir = new File(output);
|
||||
} else {
|
||||
outputDir = new File("out");
|
||||
}
|
||||
|
||||
Path outputDirPath = outputDir.toPath();
|
||||
if (!Files.exists(outputDirPath)) {
|
||||
Files.createDirectories(outputDirPath);
|
||||
}
|
||||
List<Path> generatedFiles = new ArrayList<>();
|
||||
try (final Stream<Path> templates = Files.walk(embeddedTemplatePath)) {
|
||||
templates.forEach(template -> {
|
||||
log("Found template: {}", template.toAbsolutePath());
|
||||
Path relativePath = embeddedTemplatePath.relativize(template);
|
||||
if (shouldCopy(relativePath)) {
|
||||
Path target = outputDirPath.resolve(relativePath.toString());
|
||||
generatedFiles.add(target);
|
||||
try {
|
||||
if (Files.isDirectory(template)) {
|
||||
if (Files.notExists(target)) {
|
||||
log("Creating directory: {}", target.toAbsolutePath());
|
||||
Files.createDirectories(target);
|
||||
}
|
||||
} else {
|
||||
if (target.getParent() != null && Files.notExists(target.getParent())) {
|
||||
log("Creating directory: {}", target.getParent());
|
||||
Files.createDirectories(target.getParent());
|
||||
}
|
||||
log("Copying to: {}", target.toAbsolutePath());
|
||||
Files.copy(template, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Unable to create target location '{}'.", target);
|
||||
}
|
||||
} else {
|
||||
log("Directory is excluded by library option: {}", relativePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(library) && !generatedFiles.isEmpty()) {
|
||||
Path librariesPath = outputDirPath.resolve("libraries");
|
||||
Path targetLibrary = librariesPath.resolve(library);
|
||||
String librariesPrefix = librariesPath.toString();
|
||||
if (!Files.isDirectory(targetLibrary)) {
|
||||
LOGGER.warn("The library '{}' was not extracted. Please verify the spelling and retry.", targetLibrary);
|
||||
}
|
||||
generatedFiles.stream()
|
||||
.filter(p -> p.startsWith(librariesPrefix))
|
||||
.forEach(p -> {
|
||||
if (p.startsWith(targetLibrary)) {
|
||||
// We don't care about empty directories, and not need to check directory for files.
|
||||
if (!Files.isDirectory(p)) {
|
||||
// warn if the file was not written
|
||||
if (Files.notExists(p)) {
|
||||
LOGGER.warn("An expected library file was not extracted: {}", p.toAbsolutePath());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGGER.warn("The library filter '{}' extracted an unexpected library path: {}", library, p.toAbsolutePath());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LOGGER.info("Extracted templates to '{}' directory. Refer to https://openapi-generator.tech/docs/templating for customization details.", outputDirPath);
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
LOGGER.error("Unable to load embedded template directory.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void log(String format, Object... arguments) {
|
||||
if (verbose) {
|
||||
LOGGER.info(format, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCopy(Path relativePath) {
|
||||
String path = relativePath.toString();
|
||||
if (StringUtils.isNotEmpty(library) && path.contains("libraries")) {
|
||||
if (pattern == null) {
|
||||
pattern = Pattern.compile(String.format(Locale.ROOT, "libraries[/\\\\]{1}%s[/\\\\]{1}.*", Pattern.quote(library)));
|
||||
}
|
||||
|
||||
return pattern.matcher(path).matches();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.openapitools.codegen.cmd;
|
||||
|
||||
import io.airlift.airline.Cli;
|
||||
import org.testng.Assert;
|
||||
import org.testng.ITestContext;
|
||||
import org.testng.annotations.BeforeTest;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class AuthorTemplateTest {
|
||||
Path outputDirectory;
|
||||
|
||||
@BeforeTest
|
||||
public void setUp(ITestContext ctx) throws IOException {
|
||||
outputDirectory = Files.createTempDirectory("AuthorTemplateTest");
|
||||
outputDirectory.toFile().deleteOnExit();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void smokeTestAuthorTemplateCommand(){
|
||||
Cli.CliBuilder<Runnable> builder = createBuilder();
|
||||
String[] arguments = new String[]{
|
||||
"author",
|
||||
"template",
|
||||
"-g",
|
||||
"java",
|
||||
"--library",
|
||||
"webclient",
|
||||
"--output",
|
||||
outputDirectory.toAbsolutePath().toString()
|
||||
};
|
||||
builder.build().parse(arguments).run();
|
||||
|
||||
// spot check root files
|
||||
Assert.assertTrue(Files.exists(outputDirectory.resolve("ApiClient.mustache")));
|
||||
Assert.assertTrue(Files.exists(outputDirectory.resolve("api_doc.mustache")));
|
||||
Assert.assertTrue(Files.exists(outputDirectory.resolve("pom.mustache")));
|
||||
|
||||
Assert.assertTrue(Files.exists(outputDirectory.resolve("auth/OAuth.mustache")));
|
||||
|
||||
// check libraries files and subdirectories
|
||||
Assert.assertTrue(Files.exists(outputDirectory.resolve("libraries/webclient/ApiClient.mustache")));
|
||||
Assert.assertTrue(Files.exists(outputDirectory.resolve("libraries/webclient/pom.mustache")));
|
||||
Assert.assertTrue(Files.exists(outputDirectory.resolve("libraries/webclient/auth/OAuth.mustache")));
|
||||
|
||||
// check non-existence of unselected libraries
|
||||
Assert.assertFalse(Files.exists(outputDirectory.resolve("libraries/feign/build.gradle.mustache")));
|
||||
Assert.assertFalse(Files.exists(outputDirectory.resolve("libraries/feign/auth/OAuth.mustache")));
|
||||
|
||||
Assert.assertFalse(Files.exists(outputDirectory.resolve("libraries/jersey2/api_doc.mustache")));
|
||||
Assert.assertFalse(Files.exists(outputDirectory.resolve("libraries/jersey2/auth/HttpBasicAuth.mustache")));
|
||||
|
||||
Assert.assertFalse(Files.exists(outputDirectory.resolve("libraries/okhttp-gson/api.mustache")));
|
||||
Assert.assertFalse(Files.exists(outputDirectory.resolve("libraries/okhttp-gson/auth/RetryingOAuth.mustache")));
|
||||
}
|
||||
|
||||
private Cli.CliBuilder<Runnable> createBuilder(){
|
||||
Cli.CliBuilder<Runnable> builder = new Cli.CliBuilder<>("openapi-generator-cli");
|
||||
|
||||
builder.withGroup("author")
|
||||
.withDescription("Utilities for authoring generators or customizing templates.")
|
||||
.withDefaultCommand(HelpCommand.class)
|
||||
.withCommands(AuthorTemplate.class);
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user