From 8ba444141ecabc5206bb0a1e3f34e4155db68dcb Mon Sep 17 00:00:00 2001 From: MerkushevKirill Date: Wed, 25 Mar 2015 01:59:55 +0300 Subject: [PATCH] add - new command line interface in distribution module can replace Codegen and MetaGenerator classes. Provides a bit different cli interface based on refactored pom from #541, affects #349 ``` usage: swagger [] The most commonly used swagger commands are: generate Generate code with chosen lang help Display help information langs Shows available langs meta MetaGenerator. Generator for creating a new template set and configuration for Codegen. The output will be based on the language you specify, and includes default templates to include. ``` **generate** and **meta** commands have same as current cli options --- modules/swagger-codegen-distribution/pom.xml | 173 ++++++++++-------- .../swagger/codegen/SwaggerCodegen.java | 30 +++ .../wordnik/swagger/codegen/cmd/Generate.java | 120 ++++++++++++ .../wordnik/swagger/codegen/cmd/Langs.java | 23 +++ .../com/wordnik/swagger/codegen/cmd/Meta.java | 144 +++++++++++++++ 5 files changed, 411 insertions(+), 79 deletions(-) create mode 100644 modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/SwaggerCodegen.java create mode 100644 modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Generate.java create mode 100644 modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Langs.java create mode 100644 modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Meta.java diff --git a/modules/swagger-codegen-distribution/pom.xml b/modules/swagger-codegen-distribution/pom.xml index 634ad084c0c..aea6ed3be63 100644 --- a/modules/swagger-codegen-distribution/pom.xml +++ b/modules/swagger-codegen-distribution/pom.xml @@ -1,80 +1,95 @@ - - - com.wordnik - swagger-codegen-project - 2.1.3-M1-SNAPSHOT - ../.. - - 4.0.0 - com.wordnik - swagger-codegen-distribution - jar - swagger-codegen (executable) - 2.1.3-M1-SNAPSHOT - - src/test/scala - target/classes - target/test-classes - install - target - ${project.artifactId}-${project.version} - - - org.apache.maven.plugins - maven-jar-plugin - - - - com.wordnik.swagger.codegen.Codegen - - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.3 - - - package - - shade - - - false - true - - ${java.io.tmpdir}/dependency-reduced-pom.xml - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 2.3 - - - package - - shade - - - - - - - - - - - - - - com.wordnik - swagger-codegen - ${project.parent.version} - - + + + com.wordnik + swagger-codegen-project + 2.1.3-M1-SNAPSHOT + ../.. + + 4.0.0 + + swagger-codegen-distribution + jar + + swagger-codegen (executable) + + + swagger-cli + + + org.apache.maven.plugins + maven-jar-plugin + + + + com.wordnik.swagger.codegen.SwaggerCodegen + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + reduced-pom + package + + shade + + + false + true + + ${java.io.tmpdir}/dependency-reduced-pom.xml + + + + + process-resources + package + + shade + + + + + + + + + + + + + + + + com.wordnik + swagger-codegen + ${project.version} + + + + + io.airlift + airline + 0.7 + + + + com.googlecode.lambdaj + lambdaj + 2.3.3 + + + + org.slf4j + slf4j-simple + ${slf4j-version} + + + + \ No newline at end of file diff --git a/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/SwaggerCodegen.java b/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/SwaggerCodegen.java new file mode 100644 index 00000000000..61bb55c54b0 --- /dev/null +++ b/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/SwaggerCodegen.java @@ -0,0 +1,30 @@ +package com.wordnik.swagger.codegen; + +import com.wordnik.swagger.codegen.cmd.Generate; +import com.wordnik.swagger.codegen.cmd.Langs; +import com.wordnik.swagger.codegen.cmd.Meta; +import io.airlift.airline.Cli; +import io.airlift.airline.Help; + +/** + * User: lanwen + * Date: 24.03.15 + * Time: 17:56 + */ +public class SwaggerCodegen { + + + public static void main(String[] args) { + Cli.CliBuilder builder = Cli.builder("swagger") + .withDescription("Swagger code generator CLI. More info on swagger.io") + .withDefaultCommand(Langs.class) + .withCommands( + Generate.class, + Meta.class, + Langs.class, + Help.class + ); + + builder.build().parse(args).run(); + } +} diff --git a/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Generate.java b/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Generate.java new file mode 100644 index 00000000000..94523867bbe --- /dev/null +++ b/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Generate.java @@ -0,0 +1,120 @@ +package com.wordnik.swagger.codegen.cmd; + +import com.wordnik.swagger.codegen.ClientOptInput; +import com.wordnik.swagger.codegen.ClientOpts; +import com.wordnik.swagger.codegen.CodegenConfig; +import com.wordnik.swagger.codegen.DefaultGenerator; +import com.wordnik.swagger.models.Swagger; +import io.airlift.airline.Command; +import io.airlift.airline.Option; +import io.swagger.parser.SwaggerParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.ServiceLoader; + +import static java.util.ServiceLoader.load; +import static org.apache.commons.lang3.StringUtils.isNotEmpty; + +/** + * User: lanwen + * Date: 24.03.15 + * Time: 20:22 + */ + +@Command(name = "generate", description = "Generate code with chosen lang") +public class Generate implements Runnable { + + public static final Logger LOG = LoggerFactory.getLogger(Generate.class); + + public static final String TEMPLATE_DIR_PARAM = "templateDir"; + + @Option(name = {"-v", "--verbose"}, description = "verbose mode") + public boolean verbose; + + @Option(name = {"-l", "--lang"}, title = "language", required = true, + description = "client language to generate (maybe class name in classpath, required)") + public String lang; + + @Option(name = {"-o", "--output"}, title = "output directory", + description = "where to write the generated files (current dir by default)") + public String output = ""; + + @Option(name = {"-i", "--input-spec"}, title = "spec file", required = true, + description = "location of the swagger spec, as URL or file (required)") + public String spec; + + @Option(name = {"-t", "--template-dir"}, title = "template directory", + description = "folder containing the template files") + public String templateDir; + + @Option(name = {"-a", "--auth"}, title = "authorization", + description = "adds authorization headers when fetching the swagger definitions remotely. " + + "Pass in a URL-encoded string of name:header with a comma separating multiple values") + public String auth; + + @Override + public void run() { + verbosed(verbose); + + ClientOptInput input = new ClientOptInput(); + + if (isNotEmpty(auth)) { + input.setAuth(auth); + } + + CodegenConfig config = forName(lang); + config.setOutputDir(new File(output).getAbsolutePath()); + + if (null != templateDir) { + config.additionalProperties().put(TEMPLATE_DIR_PARAM, new File(templateDir).getAbsolutePath()); + } + + input.setConfig(config); + + Swagger swagger = new SwaggerParser().read(spec, input.getAuthorizationValues(), true); + new DefaultGenerator().opts(input.opts(new ClientOpts()).swagger(swagger)).generate(); + } + + /** + * If true parameter, adds system properties which enables debug mode in generator + * @param verbose - if true, enables debug mode + */ + private void verbosed(boolean verbose) { + if (!verbose) { + return; + } + LOG.info("\nVERBOSE MODE: ON. Additional debug options are injected" + + "\n - [debugSwagger] prints the swagger specification as interpreted by the codegen" + + "\n - [debugModels] prints models passed to the template engine" + + "\n - [debugOperations] prints operations passed to the template engine" + + "\n - [debugSupportingFiles] prints additional data passed to the template engine"); + + System.setProperty("debugSwagger", ""); + System.setProperty("debugModels", ""); + System.setProperty("debugOperations", ""); + System.setProperty("debugSupportingFiles", ""); + } + + /** + * Tries to load config class with SPI first, then with class name directly from classpath + * @param name name of config, or full qualified class name in classpath + * @return config class + */ + private static CodegenConfig forName(String name) { + ServiceLoader loader = load(CodegenConfig.class); + for (CodegenConfig config : loader) { + if (config.getName().equals(name)) { + return config; + } + } + + // else try to load directly + try { + return (CodegenConfig) Class.forName(name).newInstance(); + } catch (Exception e) { + throw new RuntimeException("Can't load config class with name ".concat(name), e); + } + } +} diff --git a/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Langs.java b/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Langs.java new file mode 100644 index 00000000000..7942804d434 --- /dev/null +++ b/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Langs.java @@ -0,0 +1,23 @@ +package com.wordnik.swagger.codegen.cmd; + +import ch.lambdaj.collection.LambdaIterable; +import com.wordnik.swagger.codegen.CodegenConfig; +import io.airlift.airline.Command; + +import static ch.lambdaj.Lambda.on; +import static ch.lambdaj.collection.LambdaCollections.with; +import static java.util.ServiceLoader.load; + +/** + * User: lanwen + * Date: 24.03.15 + * Time: 20:25 + */ +@Command(name = "langs", description = "Shows available langs") +public class Langs implements Runnable { + @Override + public void run() { + LambdaIterable langs = with(load(CodegenConfig.class)).extract(on(CodegenConfig.class).getName()); + System.out.printf("Available languages: %s%n", langs); + } +} diff --git a/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Meta.java b/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Meta.java new file mode 100644 index 00000000000..5f070c43bfd --- /dev/null +++ b/modules/swagger-codegen-distribution/src/main/java/com/wordnik/swagger/codegen/cmd/Meta.java @@ -0,0 +1,144 @@ +package com.wordnik.swagger.codegen.cmd; + +import ch.lambdaj.function.convert.Converter; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.samskivert.mustache.Mustache; +import com.wordnik.swagger.codegen.DefaultGenerator; +import com.wordnik.swagger.codegen.SupportingFile; +import io.airlift.airline.Command; +import io.airlift.airline.Option; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.util.List; +import java.util.Map; + +import static ch.lambdaj.collection.LambdaCollections.with; +import static com.google.common.base.Joiner.on; + +/** + * User: lanwen + * Date: 24.03.15 + * Time: 20:22 + */ + +@Command(name = "meta", description = "MetaGenerator. Generator for creating a new template set " + + "and configuration for Codegen. The output will be based on the language you " + + "specify, and includes default templates to include.") +public class Meta implements Runnable { + + public static final Logger LOG = LoggerFactory.getLogger(Meta.class); + + public static final String TEMPLATE_DIR_CLASSPATH = "codegen"; + public static final String MUSTACHE_EXTENSION = ".mustache"; + + @Option(name = {"-o", "--output"}, title = "output directory", + description = "where to write the generated files (current dir by default)") + public String outputFolder = ""; + + @Option(name = {"-n", "--name"}, title = "name", + description = "the human-readable name of the generator") + public String name = "default"; + + @Option(name = {"-p", "--package"}, title = "package", + description = "the package to put the main class into (defaults to com.wordnik.swagger.codegen)") + public String targetPackage = "com.wordnik.swagger.codegen"; + + @Override + public void run() { + final File targetDir = new File(outputFolder); + LOG.info("writing to folder [{}]", targetDir.getAbsolutePath()); + + String mainClass = StringUtils.capitalize(name) + "Generator"; + + List supportingFiles = ImmutableList.of( + new SupportingFile("pom.mustache", "", "pom.xml"), + new SupportingFile("generatorClass.mustache", + on(File.separator).join("src/main/java", asPath(targetPackage)), mainClass.concat(".java")), + new SupportingFile("README.mustache", "", "README.md"), + new SupportingFile("api.template", "src/main/resources" + File.separator + name, "api.mustache"), + new SupportingFile("model.template", "src/main/resources" + File.separator + name, "model.mustache"), + new SupportingFile("services.mustache", + "src/main/resources/META-INF/services", "com.wordnik.swagger.codegen.CodegenConfig") + ); + + Map data = new ImmutableMap.Builder() + .put("generatorPackage", targetPackage) + .put("generatorClass", mainClass) + .put("name", name) + .put("fullyQualifiedGeneratorClass", targetPackage + "." + mainClass).build(); + + + with(supportingFiles).convert(processFiles(targetDir, data)); + } + + /** + * Converter method to process supporting files: execute with mustache, + * or simply copy to destination directory + * @param targetDir - destination directory + * @param data - map with additional params needed to process templates + * @return converter object to pass to lambdaj + */ + private Converter processFiles(final File targetDir, final Map data) { + return new Converter() { + private DefaultGenerator generator = new DefaultGenerator(); + + @Override + public File convert(SupportingFile support) { + try { + File destinationFolder = new File(new File(targetDir.getAbsolutePath()), support.folder); + File outputFile = new File(destinationFolder, support.destinationFilename); + + String template = generator + .readTemplate(new File(TEMPLATE_DIR_CLASSPATH, support.templateFile).getPath()); + String formatted = template; + + if (support.templateFile.endsWith(MUSTACHE_EXTENSION)) { + LOG.info("writing file to {}", outputFile.getAbsolutePath()); + formatted = Mustache.compiler().withLoader(loader(generator)) + .defaultValue("") + .compile(template) + .execute(data); + } else { + LOG.info("copying file to {}", outputFile.getAbsolutePath()); + } + + FileUtils.writeStringToFile(outputFile, formatted); + return outputFile; + + } catch (IOException e) { + throw new RuntimeException("Can't generate project", e); + } + } + }; + } + + /** + * Creates mustache loader for template using classpath loader + * @param generator - class with reader getter + * @return loader for template + */ + private Mustache.TemplateLoader loader(final DefaultGenerator generator) { + return new Mustache.TemplateLoader() { + public Reader getTemplate(String name) { + return generator.getTemplateReader(TEMPLATE_DIR_CLASSPATH + + File.separator + name.concat(MUSTACHE_EXTENSION)); + } + }; + } + + /** + * Converts package name to path on file system + * @param packageName - package name to convert + * @return relative path + */ + private String asPath(String packageName) { + return packageName.replace(".", File.separator); + } +}