Skip to content

Support for file based config. Implementation for #616. #805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ NAME

SYNOPSIS
swagger generate [(-a <authorization> | --auth <authorization>)]
[(-c <configuration file> | --config <configuration file>)]
[-D <system properties>]
(-i <spec file> | --input-spec <spec file>)
(-l <language> | --lang <language>)
[(-o <output directory> | --output <output directory>)]
Expand All @@ -72,6 +74,16 @@ OPTIONS
remotely. Pass in a URL-encoded string of name:header with a comma
separating multiple values

-c <configuration file>, --config <configuration file>
Path to json configuration file. File content should be in a json
format {"optionKey":"optionValue", "optionKey1":"optionValue1"...}
Supported options can be different for each language. Run
config-help -l {lang} command for language specific config options.

-D <system properties>
sets specified system properties in the format of
name=value,name=value

-i <spec file>, --input-spec <spec file>
location of the swagger spec, as URL or file (required)

Expand Down Expand Up @@ -166,8 +178,60 @@ SwaggerYamlGenerator.java
TizenClientCodegen.java
```

Each of these files creates reasonable defaults so you can get running quickly. But if you want to configure package names, prefixes, model folders, etc., you may want to extend these.
Each of these files creates reasonable defaults so you can get running quickly. But if you want to configure package names, prefixes, model folders, etc. you can use a json config file to pass the values.

```
java -jar modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate \
-i http://petstore.swagger.io/v2/swagger.json \
-l java \
-o samples/client/petstore/java \
-c path/to/config.json
```
Supported config options can be different per language. Running `config-help -l {lang}` will show available options.

```
java -jar modules/swagger-codegen-cli/target/swagger-codegen-cli.jarr config-help -l java
```

Output

```
CONFIG OPTIONS
modelPackage
package for generated models

apiPackage
package for generated api classes

invokerPackage
root package for generated code

groupId
groupId in generated pom.xml

artifactId
artifactId in generated pom.xml

artifactVersion
artifact version in generated pom.xml

sourceFolder
source folder for generated code
```

Your config file for java can look like

```
{
"groupId":"com.my.company",
"artifactId":"MyClent",
"artifactVersion":"1.2.0"
}
```

For all the unspecified options default values will be used.

Another way to override default options is to extend config class for specific language.
To change, for example, the prefix for the Objective-C generated files, simply subclass the ObjcClientCodegen.java:

```
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wordnik.swagger.codegen;

import com.wordnik.swagger.codegen.cmd.ConfigHelp;
import com.wordnik.swagger.codegen.cmd.Generate;
import com.wordnik.swagger.codegen.cmd.Langs;
import com.wordnik.swagger.codegen.cmd.Meta;
Expand Down Expand Up @@ -27,7 +28,8 @@ public static void main(String[] args) {
Generate.class,
Meta.class,
Langs.class,
Help.class
Help.class,
ConfigHelp.class
);

builder.build().parse(args).run();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.wordnik.swagger.codegen.cmd;

import com.wordnik.swagger.codegen.CliOption;
import com.wordnik.swagger.codegen.CodegenConfig;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import java.util.ServiceLoader;
import static java.util.ServiceLoader.load;

@Command(name = "config-help", description = "Config help for chosen lang")
public class ConfigHelp implements Runnable {

@Option(name = {"-l", "--lang"}, title = "language", required = true,
description = "language to get config help for")
private String lang;

@Override
public void run() {
System.out.println();
CodegenConfig config = forName(lang);
System.out.println("CONFIG OPTIONS");
for (CliOption langCliOption : config.cliOptions()) {
System.out.println("\t" + langCliOption.getOpt());
System.out.println("\t " + langCliOption.getDescription());
System.out.println();
}
}

/**
* 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<CodegenConfig> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.wordnik.swagger.codegen.cmd;

import com.wordnik.swagger.codegen.CliOption;
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 config.Config;
import config.ConfigParser;
import io.airlift.airline.Command;
import io.airlift.airline.Option;
import io.swagger.parser.SwaggerParser;
Expand Down Expand Up @@ -57,6 +60,11 @@ public class Generate implements Runnable {
@Option( name= {"-D"}, title = "system properties", description = "sets specified system properties in " +
"the format of name=value,name=value")
private String systemProperties;

@Option( name= {"-c", "--config"}, title = "configuration file", description = "Path to json configuration file. " +
"File content should be in a json format {\"optionKey\":\"optionValue\", \"optionKey1\":\"optionValue1\"...} " +
"Supported options can be different for each language. Run config-help -l {lang} command for language specific config options.")
private String configFile;

@Override
public void run() {
Expand All @@ -76,6 +84,17 @@ public void run() {
if (null != templateDir) {
config.additionalProperties().put(TEMPLATE_DIR_PARAM, new File(templateDir).getAbsolutePath());
}

if(null != configFile){
Config genConfig = ConfigParser.read(configFile);
if (null != genConfig) {
for (CliOption langCliOption : config.cliOptions()) {
if (genConfig.hasOption(langCliOption.getOpt())) {
config.additionalProperties().put(langCliOption.getOpt(), genConfig.getOption(langCliOption.getOpt()));
}
}
}
}

input.setConfig(config);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.wordnik.swagger.codegen;

public class CliOption {
private final String opt;
private String description;

public CliOption(String opt, String description) {
this.opt = opt;
this.description = description;
}

public String getOpt() {
return opt;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,4 @@ public static CodegenConfig getConfig(String name) {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public interface CodegenConfig {
String getTypeDeclaration(Property p);
String getTypeDeclaration(String name);
void processOpts();
List<CliOption> cliOptions();
String generateExamplePath(String path, Operation operation);

Set<String> reservedWords();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,24 @@ public class DefaultCodegen {
protected String templateDir;
protected Map<String, Object> additionalProperties = new HashMap<String, Object>();
protected List<SupportingFile> supportingFiles = new ArrayList<SupportingFile>();
protected List<CliOption> cliOptions = new ArrayList<CliOption>();

public List<CliOption> cliOptions() {
return cliOptions;
}

public void processOpts(){
if(additionalProperties.containsKey("templateDir")) {
this.setTemplateDir((String)additionalProperties.get("templateDir"));
}

if(additionalProperties.containsKey("modelPackage")) {
this.setModelPackage((String)additionalProperties.get("modelPackage"));
}

if(additionalProperties.containsKey("apiPackage")) {
this.setApiPackage((String)additionalProperties.get("apiPackage"));
}
}

// override with any special post-processing
Expand Down Expand Up @@ -178,6 +191,14 @@ public void setTemplateDir(String templateDir) {
this.templateDir = templateDir;
}

public void setModelPackage(String modelPackage) {
this.modelPackage = modelPackage;
}

public void setApiPackage(String apiPackage) {
this.apiPackage = apiPackage;
}

public String toApiFilename(String name) {
return toApiName(name);
}
Expand Down Expand Up @@ -281,6 +302,9 @@ public DefaultCodegen() {
importMapping.put("LocalDateTime", "org.joda.time.*");
importMapping.put("LocalDate", "org.joda.time.*");
importMapping.put("LocalTime", "org.joda.time.*");

cliOptions.add(new CliOption("modelPackage", "package for generated models"));
cliOptions.add(new CliOption("apiPackage", "package for generated api classes"));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,6 @@ public JavaClientCodegen() {
"native", "super", "while")
);

additionalProperties.put("invokerPackage", invokerPackage);
additionalProperties.put("groupId", groupId);
additionalProperties.put("artifactId", artifactId);
additionalProperties.put("artifactVersion", artifactVersion);

supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml"));
supportingFiles.add(new SupportingFile("apiInvoker.mustache",
(sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "ApiInvoker.java"));
supportingFiles.add(new SupportingFile("JsonUtil.mustache",
(sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "JsonUtil.java"));
supportingFiles.add(new SupportingFile("apiException.mustache",
(sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "ApiException.java"));

languageSpecificPrimitives = new HashSet<String>(
Arrays.asList(
"String",
Expand All @@ -71,8 +58,65 @@ public JavaClientCodegen() {
);
instantiationTypes.put("array", "ArrayList");
instantiationTypes.put("map", "HashMap");

cliOptions.add(new CliOption("invokerPackage", "root package for generated code"));
cliOptions.add(new CliOption("groupId", "groupId in generated pom.xml"));
cliOptions.add(new CliOption("artifactId", "artifactId in generated pom.xml"));
cliOptions.add(new CliOption("artifactVersion", "artifact version in generated pom.xml"));
cliOptions.add(new CliOption("sourceFolder", "source folder for generated code"));
}

@Override
public void processOpts() {
super.processOpts();

if(additionalProperties.containsKey("invokerPackage")) {
this.setInvokerPackage((String)additionalProperties.get("invokerPackage"));
}
else{
//not set, use default to be passed to template
additionalProperties.put("invokerPackage", invokerPackage);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to introduce a general method of reading and storing the options from the provided JSON file (maybe place that method in DefaultCodegen)? So that it doesn't need to do the same thing for every language separately.

A rough thought in my mind is using a Map to store any keys and values read from the JSON config file.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems no validations have been made to these options, e.g. I could use petstore model as the modelPackage (containing a space), which will generate source files that could not compile.
It's not a big deal, and the PR is really great, allowing users customizing various options for the generated code. Just pointed it out FYI (maybe the validations would be added in the future).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@xhh I did remove the template generated comments, thanks.

RE: reading and storing config options in DefaultCodegen
Right now there are two config options supported by all of the languages, modelPackage and apiPackage, those are being read and set in the processOpts method of DefaultCodegen, all the other config options that are language specific should be managed by that lang specific config itself. If in the the future there is another config option that is supported by all the languages then it should be managed it DefaultCodegen.

RE: validation
No validation is necessary on the config options. Even if model package does not contain spaces it still can be out of sync with apiPackage and it will not compile. Using these custom config options assumes some level of competency.


if(additionalProperties.containsKey("groupId")) {
this.setGroupId((String)additionalProperties.get("groupId"));
}
else{
//not set, use to be passed to template
additionalProperties.put("groupId", groupId);
}

if(additionalProperties.containsKey("artifactId")) {
this.setArtifactId((String)additionalProperties.get("artifactId"));
}
else{
//not set, use to be passed to template
additionalProperties.put("artifactId", artifactId);
}

if(additionalProperties.containsKey("artifactVersion")) {
this.setArtifactVersion((String)additionalProperties.get("artifactVersion"));
}
else{
//not set, use to be passed to template
additionalProperties.put("artifactVersion", artifactVersion);
}

if(additionalProperties.containsKey("sourceFolder")) {
this.setSourceFolder((String)additionalProperties.get("sourceFolder"));
}

supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml"));
supportingFiles.add(new SupportingFile("apiInvoker.mustache",
(sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "ApiInvoker.java"));
supportingFiles.add(new SupportingFile("JsonUtil.mustache",
(sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "JsonUtil.java"));
supportingFiles.add(new SupportingFile("apiException.mustache",
(sourceFolder + File.separator + invokerPackage).replace(".", java.io.File.separator), "ApiException.java"));
}



@Override
public String escapeReservedWord(String name) {
return "_" + name;
Expand Down Expand Up @@ -169,5 +213,23 @@ public String toOperationId(String operationId) {
return camelize(operationId, true);
}

public void setInvokerPackage(String invokerPackage) {
this.invokerPackage = invokerPackage;
}

public void setGroupId(String groupId) {
this.groupId = groupId;
}

public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}

public void setArtifactVersion(String artifactVersion) {
this.artifactVersion = artifactVersion;
}

public void setSourceFolder(String sourceFolder) {
this.sourceFolder = sourceFolder;
}
}
Loading