Merge pull request #2899 from jimschubert/feature/codegen-ignore

Feature/codegen ignore
This commit is contained in:
wing328 2016-05-19 18:02:23 +08:00
commit 624e8ae41f
19 changed files with 1310 additions and 113 deletions

View File

@ -99,6 +99,16 @@ public abstract class AbstractGenerator {
}
}
public String readResourceContents(String resourceFilePath) {
StringBuilder sb = new StringBuilder();
Scanner scanner = new Scanner(this.getClass().getResourceAsStream(getCPResourcePath(resourceFilePath)), "UTF-8");
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
sb.append(line).append('\n');
}
return sb.toString();
}
public boolean embeddedTemplateExists(String name) {
return this.getClass().getClassLoader().getResource(getCPResourcePath(name)) != null;
}

View File

@ -184,4 +184,5 @@ public interface CodegenConfig {
String getHttpUserAgent();
String getCommonTemplateDir();
}

View File

@ -94,6 +94,7 @@ public class DefaultCodegen {
protected Map<String, String> modelDocTemplateFiles = new HashMap<String, String>();
protected String templateDir;
protected String embeddedTemplateDir;
protected String commonTemplateDir = "_common";
protected Map<String, Object> additionalProperties = new HashMap<String, Object>();
protected Map<String, Object> vendorExtensions = new HashMap<String, Object>();
protected List<SupportingFile> supportingFiles = new ArrayList<SupportingFile>();
@ -441,6 +442,14 @@ public class DefaultCodegen {
}
}
public String getCommonTemplateDir() {
return this.commonTemplateDir;
}
public void setCommonTemplateDir(String commonTemplateDir) {
this.commonTemplateDir = commonTemplateDir;
}
public Map<String, String> apiDocTemplateFiles() {
return apiDocTemplateFiles;
}

View File

@ -2,6 +2,7 @@ package io.swagger.codegen;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import io.swagger.codegen.ignore.CodegenIgnoreProcessor;
import io.swagger.models.*;
import io.swagger.models.auth.OAuth2Definition;
import io.swagger.models.auth.SecuritySchemeDefinition;
@ -24,6 +25,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
protected CodegenConfig config;
protected ClientOptInput opts;
protected Swagger swagger;
protected CodegenIgnoreProcessor ignoreProcessor;
@Override
public Generator opts(ClientOptInput opts) {
@ -31,8 +33,11 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
this.swagger = opts.getSwagger();
this.config = opts.getConfig();
this.config.additionalProperties().putAll(opts.getOpts().getProperties());
ignoreProcessor = new CodegenIgnoreProcessor(this.config.getOutputDir());
return this;
}
@ -197,7 +202,6 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
String basePath = hostBuilder.toString();
String basePathWithoutHost = swagger.getBasePath();
// resolve inline models
InlineModelResolver inlineModelResolver = new InlineModelResolver();
inlineModelResolver.flatten(swagger);
@ -305,19 +309,11 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
LOGGER.info("Skipped overwriting " + filename);
continue;
}
String templateFile = getFullTemplateFile(config, templateName);
String template = readTemplate(templateFile);
Template tmpl = Mustache.compiler()
.withLoader(new Mustache.TemplateLoader() {
@Override
public Reader getTemplate(String name) {
return getTemplateReader(getFullTemplateFile(config, name + ".mustache"));
File written = processTemplateToFile(models, templateName, filename);
if(written != null) {
files.add(written);
}
})
.defaultValue("")
.compile(template);
writeToFile(filename, tmpl.execute(models));
files.add(new File(filename));
}
if(generateModelTests) {
@ -330,19 +326,11 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
LOGGER.info("File exists. Skipped overwriting " + filename);
continue;
}
String templateFile = getFullTemplateFile(config, templateName);
String template = readTemplate(templateFile);
Template tmpl = Mustache.compiler()
.withLoader(new Mustache.TemplateLoader() {
@Override
public Reader getTemplate(String name) {
return getTemplateReader(getFullTemplateFile(config, name + ".mustache"));
File written = processTemplateToFile(models, templateName, filename);
if (written != null) {
files.add(written);
}
})
.defaultValue("")
.compile(template);
writeToFile(filename, tmpl.execute(models));
files.add(new File(filename));
}
}
@ -355,19 +343,11 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
LOGGER.info("Skipped overwriting " + filename);
continue;
}
String templateFile = getFullTemplateFile(config, templateName);
String template = readTemplate(templateFile);
Template tmpl = Mustache.compiler()
.withLoader(new Mustache.TemplateLoader() {
@Override
public Reader getTemplate(String name) {
return getTemplateReader(getFullTemplateFile(config, name + ".mustache"));
File written = processTemplateToFile(models, templateName, filename);
if (written != null) {
files.add(written);
}
})
.defaultValue("")
.compile(template);
writeToFile(filename, tmpl.execute(models));
files.add(new File(filename));
}
}
} catch (Exception e) {
@ -443,20 +423,10 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
continue;
}
String templateFile = getFullTemplateFile(config, templateName);
String template = readTemplate(templateFile);
Template tmpl = Mustache.compiler()
.withLoader(new Mustache.TemplateLoader() {
@Override
public Reader getTemplate(String name) {
return getTemplateReader(getFullTemplateFile(config, name + ".mustache"));
File written = processTemplateToFile(operation, templateName, filename);
if(written != null) {
files.add(written);
}
})
.defaultValue("")
.compile(template);
writeToFile(filename, tmpl.execute(operation));
files.add(new File(filename));
}
if(generateApiTests) {
@ -468,22 +438,14 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
LOGGER.info("File exists. Skipped overwriting " + filename);
continue;
}
String templateFile = getFullTemplateFile(config, templateName);
String template = readTemplate(templateFile);
Template tmpl = Mustache.compiler()
.withLoader(new Mustache.TemplateLoader() {
@Override
public Reader getTemplate(String name) {
return getTemplateReader(getFullTemplateFile(config, name + ".mustache"));
}
})
.defaultValue("")
.compile(template);
writeToFile(filename, tmpl.execute(operation));
files.add(new File(filename));
File written = processTemplateToFile(operation, templateName, filename);
if (written != null) {
files.add(written);
}
}
}
if(generateApiDocumentation) {
// to generate api documentation files
@ -494,20 +456,10 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
continue;
}
String templateFile = getFullTemplateFile(config, templateName);
String template = readTemplate(templateFile);
Template tmpl = Mustache.compiler()
.withLoader(new Mustache.TemplateLoader() {
@Override
public Reader getTemplate(String name) {
return getTemplateReader(getFullTemplateFile(config, name + ".mustache"));
File written = processTemplateToFile(operation, templateName, filename);
if (written != null) {
files.add(written);
}
})
.defaultValue("")
.compile(template);
writeToFile(filename, tmpl.execute(operation));
files.add(new File(filename));
}
}
@ -590,6 +542,7 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
}
}
if(shouldGenerate) {
if(ignoreProcessor.allowsFile(new File(outputFilename))) {
if (templateFile.endsWith("mustache")) {
String template = readTemplate(templateFile);
Template tmpl = Mustache.compiler()
@ -627,16 +580,57 @@ public class DefaultGenerator extends AbstractGenerator implements Generator {
}
files.add(outputFile);
}
} else {
LOGGER.info("Skipped generation of " + outputFilename + " due to rule in .swagger-codegen-ignore");
}
}
} catch (Exception e) {
throw new RuntimeException("Could not generate supporting file '" + support + "'", e);
}
}
// Consider .swagger-codegen-ignore a supporting file
// Output .swagger-codegen-ignore if it doesn't exist and wasn't explicitly created by a generator
final String swaggerCodegenIgnore = ".swagger-codegen-ignore";
String ignoreFileNameTarget = config.outputFolder() + File.separator + swaggerCodegenIgnore;
File ignoreFile = new File(ignoreFileNameTarget);
if(!ignoreFile.exists()) {
String ignoreFileNameSource = File.separator + config.getCommonTemplateDir() + File.separator + swaggerCodegenIgnore;
String ignoreFileContents = readResourceContents(ignoreFileNameSource);
try {
writeToFile(ignoreFileNameTarget, ignoreFileContents);
} catch (IOException e) {
throw new RuntimeException("Could not generate supporting file '" + swaggerCodegenIgnore + "'", e);
}
files.add(ignoreFile);
}
}
config.processSwagger(swagger);
return files;
}
private File processTemplateToFile(Map<String, Object> templateData, String templateName, String outputFilename) throws IOException {
if(ignoreProcessor.allowsFile(new File(outputFilename.replaceAll("//", "/")))) {
String templateFile = getFullTemplateFile(config, templateName);
String template = readTemplate(templateFile);
Template tmpl = Mustache.compiler()
.withLoader(new Mustache.TemplateLoader() {
@Override
public Reader getTemplate(String name) {
return getTemplateReader(getFullTemplateFile(config, name + ".mustache"));
}
})
.defaultValue("")
.compile(template);
writeToFile(outputFilename, tmpl.execute(templateData));
return new File(outputFilename);
}
LOGGER.info("Skipped generation of " + outputFilename + " due to rule in .swagger-codegen-ignore");
return null;
}
private static void processMimeTypes(List<String> mimeTypeList, Map<String, Object> operation, String source) {
if (mimeTypeList != null && mimeTypeList.size() > 0) {
List<Map<String, String>> c = new ArrayList<Map<String, String>>();

View File

@ -0,0 +1,134 @@
package io.swagger.codegen.ignore;
import com.google.common.collect.ImmutableList;
import io.swagger.codegen.ignore.rules.DirectoryRule;
import io.swagger.codegen.ignore.rules.Rule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class CodegenIgnoreProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(CodegenIgnoreProcessor.class);
private final String outputPath;
private List<Rule> exclusionRules = new ArrayList<>();
private List<Rule> inclusionRules = new ArrayList<>();
public CodegenIgnoreProcessor(String outputPath) {
this.outputPath = outputPath;
final File directory = new File(outputPath);
if(directory.exists() && directory.isDirectory()){
final File codegenIgnore = new File(directory, ".swagger-codegen-ignore");
if(codegenIgnore.exists() && codegenIgnore.isFile()){
try {
loadCodegenRules(codegenIgnore);
} catch (IOException e) {
LOGGER.error("Could not process .swagger-codegen-ignore.", e.getMessage());
}
} else {
// log info message
LOGGER.info("No .swagger-codegen-ignore file found.");
}
}
}
void loadCodegenRules(File codegenIgnore) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(codegenIgnore))) {
String line;
// NOTE: Comments that start with a : (e.g. //:) are pulled from git documentation for .gitignore
// see: https://github.com/git/git/blob/90f7b16b3adc78d4bbabbd426fb69aa78c714f71/Documentation/gitignore.txt
while ((line = reader.readLine()) != null) {
if(
//: A blank line matches no files, so it can serve as a separator for readability.
line.length() == 0
) continue;
Rule rule = Rule.create(line);
// rule could be null here if it's a COMMENT, for example
if(rule != null) {
if (Boolean.TRUE.equals(rule.getNegated())) {
inclusionRules.add(rule);
} else {
exclusionRules.add(rule);
}
}
}
}
}
public boolean allowsFile(File targetFile) {
File file = new File(new File(this.outputPath).toURI().relativize(targetFile.toURI()).getPath());
Boolean directoryExcluded = false;
Boolean exclude = false;
if(exclusionRules.size() == 0 && inclusionRules.size() == 0) {
return true;
}
// NOTE: We *must* process all exclusion rules
for (int i = 0; i < exclusionRules.size(); i++) {
Rule current = exclusionRules.get(i);
Rule.Operation op = current.evaluate(file.getPath());
switch (op){
case EXCLUDE:
exclude = true;
// Include rule can't override rules that exclude a file by some parent directory.
if(current instanceof DirectoryRule) {
directoryExcluded = true;
}
break;
case INCLUDE:
// This won't happen here.
break;
case NOOP:
break;
case EXCLUDE_AND_TERMINATE:
i = exclusionRules.size();
break;
}
}
if(exclude) {
// Only need to process inclusion rules if we've been excluded
for (int i = 0; exclude && i < inclusionRules.size(); i++) {
Rule current = inclusionRules.get(i);
Rule.Operation op = current.evaluate(file.getPath());
// At this point exclude=true means the file should be ignored.
// op == INCLUDE means we have to flip that flag.
if(op.equals(Rule.Operation.INCLUDE)) {
if(current instanceof DirectoryRule && directoryExcluded) {
// e.g
// baz/
// !foo/bar/baz/
// NOTE: Possibly surprising side effect:
// foo/bar/baz/
// !bar/
exclude = false;
} else if (!directoryExcluded) {
// e.g.
// **/*.log
// !ISSUE_1234.log
exclude = false;
}
}
}
}
return Boolean.FALSE.equals(exclude);
}
public List<Rule> getInclusionRules() {
return ImmutableList.copyOf(inclusionRules);
}
public List<Rule> getExclusionRules() {
return ImmutableList.copyOf(exclusionRules);
}
}

View File

@ -0,0 +1,20 @@
package io.swagger.codegen.ignore.rules;
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.util.List;
public class DirectoryRule extends FileRule {
private PathMatcher matcher = null;
DirectoryRule(List<Part> syntax, String definition) {
super(syntax, definition);
matcher = FileSystems.getDefault().getPathMatcher("glob:**/"+this.getPattern());
}
@Override
public Boolean matches(String relativePath) {
return matcher.matches(FileSystems.getDefault().getPath(relativePath));
}
}

View File

@ -0,0 +1,20 @@
package io.swagger.codegen.ignore.rules;
import java.util.List;
/**
* An ignore rule which matches everything.
*/
public class EverythingRule extends Rule {
EverythingRule(List<Part> syntax, String definition) {
super(syntax, definition);
}
@Override
public Boolean matches(String relativePath) {
return true;
}
@Override
protected Operation getExcludeOperation(){ return Operation.EXCLUDE_AND_TERMINATE; }
}

View File

@ -0,0 +1,20 @@
package io.swagger.codegen.ignore.rules;
import java.nio.file.FileSystems;
import java.nio.file.PathMatcher;
import java.util.List;
public class FileRule extends Rule {
private PathMatcher matcher = null;
FileRule(List<Part> syntax, String definition) {
super(syntax, definition);
matcher = FileSystems.getDefault().getPathMatcher("glob:"+this.getPattern());
}
@Override
public Boolean matches(String relativePath) {
return matcher.matches(FileSystems.getDefault().getPath(relativePath));
}
}

View File

@ -0,0 +1,134 @@
package io.swagger.codegen.ignore.rules;
import java.util.ArrayList;
import java.util.List;
public class IgnoreLineParser {
enum Token {
MATCH_ALL("**"),
MATCH_ANY("*"),
ESCAPED_EXCLAMATION("\\!"),
ESCAPED_SPACE("\\ "),
PATH_DELIM("/"),
NEGATE("!"),
TEXT(null),
DIRECTORY_MARKER("/"),
ROOTED_MARKER("/"),
COMMENT(null);
private String pattern;
Token(String pattern) {
this.pattern = pattern;
}
public String getPattern() {
return pattern;
}
}
// NOTE: Comments that start with a : (e.g. //:) are pulled from git documentation for .gitignore
// see: https://github.com/git/git/blob/90f7b16b3adc78d4bbabbd426fb69aa78c714f71/Documentation/gitignore.txt
static List<Part> parse(String text) throws ParserException {
List<Part> parts = new ArrayList<>();
StringBuilder sb = new StringBuilder();
String current = null;
String next = null;
char[] characters = text.toCharArray();
for (int i = 0, totalLength = characters.length; i < totalLength; i++) {
char character = characters[i];
current = String.valueOf(character);
next = i < totalLength - 1 ? String.valueOf(characters[i + 1]) : null;
if (i == 0) {
if ("#".equals(current)) {
//: A line starting with # serves as a comment.
parts.add(new Part(Token.COMMENT, text));
i = totalLength; // rather than early return
continue;
} else if ("!".equals(current)) {
if (i == totalLength - 1) {
throw new ParserException("Negation with no negated pattern.");
} else {
parts.add(new Part(Token.NEGATE));
continue;
}
} else if ("\\".equals(current) && "#".equals(next)) {
//: Put a backslash ("`\`") in front of the first hash for patterns
//: that begin with a hash.
// NOTE: Just push forward and drop the escape character. Falls through to TEXT token.
current = next;
next = null;
i++;
}
}
if (Token.MATCH_ANY.pattern.equals(current)) {
if (Token.MATCH_ANY.pattern.equals(next)) {
// peek ahead for invalid pattern. Slightly inefficient, but acceptable.
if ((i+2 < totalLength - 1) &&
String.valueOf(characters[i+2]).equals(Token.MATCH_ANY.pattern)) {
// It doesn't matter where we are in the pattern, *** is invalid.
throw new ParserException("The pattern *** is invalid.");
}
parts.add(new Part(Token.MATCH_ALL));
i++;
continue;
} else {
parts.add(new Part(Token.MATCH_ANY));
continue;
}
}
if (i == 0 && Token.ROOTED_MARKER.pattern.equals(current)) {
parts.add(new Part(Token.ROOTED_MARKER));
continue;
}
if ("\\".equals(current) && " ".equals(next)) {
parts.add(new Part(Token.ESCAPED_SPACE));
i++;
continue;
} else if ("\\".equals(current) && "!".equals(next)) {
parts.add(new Part(Token.ESCAPED_EXCLAMATION));
i++;
continue;
}
if (Token.PATH_DELIM.pattern.equals(current)) {
if (i != totalLength - 1) {
if (sb.length() > 0) {
parts.add(new Part(Token.TEXT, sb.toString()));
sb.delete(0, sb.length());
}
parts.add(new Part(Token.PATH_DELIM));
if(Token.PATH_DELIM.pattern.equals(next)) {
// ignore doubled path delims. NOTE: doesn't do full lookahead, so /// will result in //
i++;
}
continue;
} else if (i == totalLength - 1) {
parts.add(new Part(Token.TEXT, sb.toString()));
sb.delete(0, sb.length());
parts.add(new Part(Token.DIRECTORY_MARKER));
continue;
}
}
sb.append(current);
}
if (sb.length() > 0) {
// NOTE: All spaces escaped spaces are a special token, ESCAPED_SPACE
//: Trailing spaces are ignored unless they are quoted with backslash ("`\`")
parts.add(new Part(Token.TEXT, sb.toString().trim()));
}
return parts;
}
}

View File

@ -0,0 +1,26 @@
package io.swagger.codegen.ignore.rules;
import java.util.List;
public class InvalidRule extends Rule {
private final String reason;
InvalidRule(List<Part> syntax, String definition, String reason) {
super(syntax, definition);
this.reason = reason;
}
@Override
public Boolean matches(String relativePath) {
return null;
}
@Override
public Operation evaluate(String relativePath) {
return Operation.NOOP;
}
public String getReason() {
return reason;
}
}

View File

@ -0,0 +1,15 @@
package io.swagger.codegen.ignore.rules;
public class ParserException extends Exception {
/**
* Constructs a new exception with the specified detail message. The
* cause is not initialized, and may subsequently be initialized by
* a call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public ParserException(String message) {
super(message);
}
}

View File

@ -0,0 +1,24 @@
package io.swagger.codegen.ignore.rules;
class Part {
private final IgnoreLineParser.Token token;
private final String value;
public Part(IgnoreLineParser.Token token, String value) {
this.token = token;
this.value = value;
}
public Part(IgnoreLineParser.Token token) {
this.token = token;
this.value = token.getPattern();
}
public IgnoreLineParser.Token getToken() {
return token;
}
public String getValue() {
return value;
}
}

View File

@ -0,0 +1,58 @@
package io.swagger.codegen.ignore.rules;
import java.util.List;
import java.util.regex.Pattern;
/**
* A special case rule which matches files only if they're located
* in the same directory as the .swagger-codegen-ignore file.
*/
public class RootedFileRule extends Rule {
private String definedFilename = null;
private String definedExtension = null;
RootedFileRule(List<Part> syntax, String definition) {
super(syntax, definition);
int separatorIndex = definition.lastIndexOf(".");
definedFilename = getFilenamePart(definition, separatorIndex);
definedExtension = getExtensionPart(definition, separatorIndex);
}
private String getFilenamePart(final String input, int stopIndex){
return input.substring('/' == input.charAt(0) ? 1 : 0, stopIndex > 0 ? stopIndex : input.length());
}
private String getExtensionPart(final String input, int stopIndex) {
return input.substring(stopIndex > 0 ? stopIndex+1: input.length(), input.length());
}
@Override
public Boolean matches(String relativePath) {
// NOTE: Windows-style separator isn't supported, so File.pathSeparator would be incorrect here.
// NOTE: lastIndexOf rather than contains because /file.txt is acceptable while path/file.txt is not.
// relativePath will be passed by CodegenIgnoreProcessor and is relative to .codegen-ignore.
boolean isSingleFile = relativePath.lastIndexOf("/") <= 0;
if(isSingleFile) {
int separatorIndex = relativePath.lastIndexOf(".");
final String filename = getFilenamePart(relativePath, separatorIndex);
final String extension = getExtensionPart(relativePath, separatorIndex);
boolean extensionMatches = definedExtension.equals(extension) || definedExtension.equals(IgnoreLineParser.Token.MATCH_ANY.getPattern());
if(extensionMatches && definedFilename.contains(IgnoreLineParser.Token.MATCH_ANY.getPattern())) {
// TODO: Evaluate any other escape requirements here.
Pattern regex = Pattern.compile(
definedFilename
.replaceAll(Pattern.quote("."), "\\\\Q.\\\\E")
.replaceAll(Pattern.quote("*"), ".*?") // non-greedy match on 0+ any character
);
return regex.matcher(filename).matches();
}
return extensionMatches && definedFilename.equals(filename);
}
return false;
}
}

View File

@ -0,0 +1,175 @@
package io.swagger.codegen.ignore.rules;
import java.util.List;
public abstract class Rule {
public enum Operation {EXCLUDE, INCLUDE, NOOP, EXCLUDE_AND_TERMINATE}
// The original rule
private final String definition;
private final List<Part> syntax;
Rule(List<Part> syntax, String definition) {
this.syntax = syntax;
this.definition = definition;
}
public abstract Boolean matches(String relativePath);
public String getDefinition() {
return this.definition;
}
protected String getPattern() {
if(syntax == null) return this.definition;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < syntax.size(); i++) {
Part current = syntax.get(i);
switch(current.getToken()){
case MATCH_ALL:
case MATCH_ANY:
case ESCAPED_EXCLAMATION:
case ESCAPED_SPACE:
case PATH_DELIM:
case TEXT:
case DIRECTORY_MARKER:
sb.append(current.getValue());
break;
case NEGATE:
case ROOTED_MARKER:
case COMMENT:
break;
}
}
return sb.toString();
}
/**
* Whether or not the rule should be negated. !foo means foo should be removed from previous matches.
* Example: **\/*.bak excludes all backup. Adding !/test.bak will include test.bak in the project root.
* <p>
* NOTE: It is not possible to re-include a file if a parent directory of that file is excluded.
*/
public Boolean getNegated() {
return this.syntax != null && this.syntax.size() > 0 && this.syntax.get(0).getToken() == IgnoreLineParser.Token.NEGATE;
}
public Operation evaluate(String relativePath) {
if (Boolean.TRUE.equals(matches(relativePath))) {
if(Boolean.TRUE.equals(this.getNegated())) {
return this.getIncludeOperation();
}
return this.getExcludeOperation();
}
return Operation.NOOP;
}
protected Operation getIncludeOperation(){ return Operation.INCLUDE; }
protected Operation getExcludeOperation(){ return Operation.EXCLUDE; }
public static Rule create(String definition) {
// NOTE: Comments that start with a : (e.g. //:) are pulled from git documentation for .gitignore
// see: https://github.com/git/git/blob/90f7b16b3adc78d4bbabbd426fb69aa78c714f71/Documentation/gitignore.txt
Rule rule = null;
if (definition.equals(".")) {
return new InvalidRule(null, definition, "Pattern '.' is invalid.");
} else if (definition.equals("!.")) {
return new InvalidRule(null, definition, "Pattern '!.' is invalid.");
} else if (definition.startsWith("..")) {
return new InvalidRule(null, definition, "Pattern '..' is invalid.");
}
try {
List<Part> result = IgnoreLineParser.parse(definition);
Boolean directoryOnly = null;
if (result.size() == 0) {
return rule;
} else if (result.size() == 1) {
// single-character filename only
Part part = result.get(0);
if (IgnoreLineParser.Token.MATCH_ANY.equals(part.getToken())) {
rule = new RootedFileRule(result, definition);
} else {
rule = new FileRule(result, definition);
}
} else {
IgnoreLineParser.Token head = result.get(0).getToken();
//: An optional prefix "`!`" which negates the pattern; any
//: matching file excluded by a previous pattern will become
//: included again. It is not possible to re-include a file if a parent
//: directory of that file is excluded. Git doesn't list excluded
//: directories for performance reasons, so any patterns on contained
//: files have no effect, no matter where they are defined.
//: Put a backslash ("`\`") in front of the first "`!`" for patterns
//: that begin with a literal "`!`", for example, "`\!important!.txt`".
// see this.getNegated();
//: If the pattern ends with a slash, it is removed for the
//: purpose of the following description, but it would only find
//: a match with a directory. In other words, `foo/` will match a
//: directory `foo` and paths underneath it, but will not match a
//: regular file or a symbolic link `foo` (this is consistent
//: with the way how pathspec works in general in Git).
directoryOnly = IgnoreLineParser.Token.DIRECTORY_MARKER.equals(result.get(result.size() - 1).getToken());
if (directoryOnly) {
rule = new DirectoryRule(result, definition);
} else if (IgnoreLineParser.Token.PATH_DELIM.equals(head)) {
//: A leading slash matches the beginning of the pathname.
//: For example, "/{asterisk}.c" matches "cat-file.c" but not
//: "mozilla-sha1/sha1.c".
rule = new RootedFileRule(result, definition);
} else {
// case 1
//: If the pattern does not contain a slash '/', Git treats it as
//: a shell glob pattern and checks for a match against the
//: pathname relative to the location of the `.gitignore` file
//: (relative to the toplevel of the work tree if not from a
//: `.gitignore` file).
// case 2
//: Otherwise, Git treats the pattern as a shell glob suitable
//: for consumption by fnmatch(3) with the FNM_PATHNAME flag:
//: wildcards in the pattern will not match a / in the pathname.
//: For example, "Documentation/{asterisk}.html" matches
//: "Documentation/git.html" but not "Documentation/ppc/ppc.html"
//: or "tools/perf/Documentation/perf.html".
// case 3
//: Two consecutive asterisks ("`**`") in patterns matched against
//: full pathname may have special meaning:
//:
//: - A leading "`**`" followed by a slash means match in all
//: directories. For example, "`**/foo`" matches file or directory
//: "`foo`" anywhere, the same as pattern "`foo`". "`**/foo/bar`"
//: matches file or directory "`bar`" anywhere that is directly
//: under directory "`foo`".
//:
//: - A trailing "`/**`" matches everything inside. For example,
//: "`abc/**`" matches all files inside directory "`abc`", relative
//: to the location of the `.gitignore` file, with infinite depth.
//:
//: - A slash followed by two consecutive asterisks then a slash
//: matches zero or more directories. For example, "`a/**/b`"
//: matches "`a/b`", "`a/x/b`", "`a/x/y/b`" and so on.
//:
//: - Other consecutive asterisks are considered invalid.
rule = new FileRule(result, definition);
}
}
} catch (ParserException e) {
e.printStackTrace();
return new InvalidRule(null, definition, e.getMessage());
}
return rule;
}
}

View File

@ -0,0 +1,23 @@
# Swagger Codegen Ignore
# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# Thsi matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,21 @@
package io.swagger.codegen.ignore;
import org.testng.annotations.Test;
public class CodegenIgnoreProcessorTest {
@Test
public void loadCodegenRules() throws Exception {
}
@Test
public void getInclusionRules() throws Exception {
}
@Test
public void getExclusionRules() throws Exception {
}
}

View File

@ -0,0 +1,70 @@
package io.swagger.codegen.ignore.rules;
import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.List;
import static org.testng.Assert.*;
public class FileRuleTest {
@Test
public void testMatchComplex() throws Exception {
// Arrange
final String definition = "path/to/**/complex/*.txt";
final String relativePath = "path/to/some/nested/complex/xyzzy.txt";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "path"),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.TEXT, "to"),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.MATCH_ALL),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.TEXT, "complex"),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.MATCH_ANY),
new Part(IgnoreLineParser.Token.TEXT, ".txt")
);
Rule rule = new FileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertTrue(actual);
}
@Test
public void testNonMatchComplex() throws Exception {
// Arrange
final String definition = "path/to/**/complex/*.txt";
final String relativePath = "path/to/some/nested/invalid/xyzzy.txt";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "path"),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.TEXT, "to"),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.MATCH_ALL),
new Part(IgnoreLineParser.Token.TEXT, "complex"),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.MATCH_ANY),
new Part(IgnoreLineParser.Token.TEXT, ".txt")
);
Rule rule = new FileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertFalse(actual);
}
}

View File

@ -0,0 +1,158 @@
package io.swagger.codegen.ignore.rules;
import org.testng.annotations.Test;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import static org.testng.Assert.*;
public class IgnoreLineParserTest {
private IgnoreLineParser.Token verifyInputToSingleToken(final String input, IgnoreLineParser.Token token) throws ParserException {
// Act
List<Part> result = IgnoreLineParser.parse(input);
// Assert
assertNotNull(result);
assertEquals(result.size(), 1);
IgnoreLineParser.Token actual = result.get(0).getToken();
assertEquals(actual, token);
return actual;
}
@Test
public void parseMatchAll() throws Exception {
verifyInputToSingleToken("**", IgnoreLineParser.Token.MATCH_ALL);
}
@Test
public void parseMatchAny() throws Exception {
verifyInputToSingleToken("*", IgnoreLineParser.Token.MATCH_ANY);
}
@Test(expectedExceptions = ParserException.class,
expectedExceptionsMessageRegExp = "Negation with no negated pattern\\.")
public void parseNegate() throws Exception {
verifyInputToSingleToken("!", IgnoreLineParser.Token.NEGATE);
// Assert
fail("Expected simple pattern '!' to throw a ParserException.");
}
@Test
public void parseComment() throws Exception {
// Arrange
final String input = "# This is a comment";
Part actual = null;
// Act
List<Part> result = IgnoreLineParser.parse(input);
// Assert
assertEquals(result.size(), 1);
actual = result.get(0);
assertEquals(actual.getToken(), IgnoreLineParser.Token.COMMENT);
assertEquals(actual.getValue(), input);
}
@Test
public void parseEscapedExclamation() throws Exception {
final String input = "\\!";
verifyInputToSingleToken(input, IgnoreLineParser.Token.ESCAPED_EXCLAMATION);
}
@Test
public void parseEscapedSpace() throws Exception {
final String input = "\\ ";
verifyInputToSingleToken(input, IgnoreLineParser.Token.ESCAPED_SPACE);
}
@Test
public void parseDirectoryMarker() throws Exception {
// Arrange
final String input = "foo/";
Part actual = null;
// Act
List<Part> result = IgnoreLineParser.parse(input);
// Assert
assertEquals(result.size(), 2);
actual = result.get(0);
assertEquals(actual.getToken(), IgnoreLineParser.Token.TEXT);
assertEquals(actual.getValue(), "foo");
actual = result.get(1);
assertEquals(actual.getToken(), IgnoreLineParser.Token.DIRECTORY_MARKER);
}
@Test
public void parseRooted() throws Exception {
// Arrange
final String input = "/abcd";
Part actual = null;
// Act
List<Part> result = IgnoreLineParser.parse(input);
// Assert
assertEquals(result.size(), 2);
actual = result.get(0);
assertEquals(actual.getToken(), IgnoreLineParser.Token.ROOTED_MARKER);
actual = result.get(1);
assertEquals(actual.getToken(), IgnoreLineParser.Token.TEXT);
assertEquals(actual.getValue(), "abcd");
}
@Test
public void parseComplex() throws Exception {
// Arrange
final String input = "**/abcd/**/foo/bar/sample.txt";
Part current = null;
// Act
Queue<Part> result = new LinkedList<>(IgnoreLineParser.parse(input));
// Assert
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.MATCH_ALL);
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.PATH_DELIM);
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.TEXT);
assertEquals(current.getValue(), "abcd");
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.PATH_DELIM);
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.MATCH_ALL);
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.PATH_DELIM);
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.TEXT);
assertEquals(current.getValue(), "foo");
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.PATH_DELIM);
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.TEXT);
assertEquals(current.getValue(), "bar");
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.PATH_DELIM);
current = result.remove();
assertEquals(current.getToken(), IgnoreLineParser.Token.TEXT);
assertEquals(current.getValue(), "sample.txt");
}
@Test(expectedExceptions = ParserException.class,
expectedExceptionsMessageRegExp = "The pattern \\*\\*\\* is invalid\\.")
public void parseTripleStarPattern() throws Exception {
// Arrange
final String input = "should/throw/***/anywhere";
// Act
List<Part> result = IgnoreLineParser.parse(input);
// Assert
fail("Expected pattern containing '***' to throw a ParserException.");
}
}

View File

@ -0,0 +1,285 @@
package io.swagger.codegen.ignore.rules;
import org.testng.annotations.Test;
import java.util.Arrays;
import java.util.List;
import static org.testng.Assert.*;
public class RootedFileRuleTest {
@Test
public void testMatchFilenameOnly() throws Exception {
// Arrange
final String definition = "/foo";
final String relativePath = "foo";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertTrue(actual);
}
@Test
public void testNonMatchFilenameOnly() throws Exception {
// Arrange
final String definition = "/foo";
final String relativePath = "bar";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertFalse(actual);
}
@Test
public void testMatchFilenameAndExtension() throws Exception {
// Arrange
final String definition = "/foo.txt";
final String relativePath = "foo.txt";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo.txt")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertTrue(actual);
}
@Test
public void testNonMatchFilenameAndExtension() throws Exception {
// Arrange
final String definition = "/foo.txt";
final String relativePath = "bar.baz";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo.txt")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertFalse(actual);
}
@Test
public void testMatchFilenameWithGlob() throws Exception {
// Arrange
final String definition = "/foo*";
final String relativePath = "foobarbaz";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo"),
new Part(IgnoreLineParser.Token.MATCH_ANY)
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertTrue(actual);
}
@Test
public void testNonMatchFilenameWithGlob() throws Exception {
// Arrange
final String definition = "/foo*";
final String relativePath = "boobarbaz";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo"),
new Part(IgnoreLineParser.Token.MATCH_ANY)
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertFalse(actual);
}
@Test
public void testMatchFilenameAndExtensionWithFilenameGlob() throws Exception {
// Arrange
final String definition = "/foo*.txt";
final String relativePath = "foobarbaz.txt";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo"),
new Part(IgnoreLineParser.Token.MATCH_ANY),
new Part(IgnoreLineParser.Token.TEXT, ".txt")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertTrue(actual);
}
@Test
public void testNonMatchFilenameAndExtensionWithFilenameGlob() throws Exception {
// Arrange
final String definition = "/foo*qux.txt";
final String relativePath = "foobarbaz.txt";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo"),
new Part(IgnoreLineParser.Token.MATCH_ANY),
new Part(IgnoreLineParser.Token.TEXT, "qux.txt")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertFalse(actual);
}
@Test
public void testMatchFilenameAndExtensionWithExtensionGlob() throws Exception {
// Arrange
final String definition = "/foo.*";
final String relativePath = "foo.bak";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo."),
new Part(IgnoreLineParser.Token.MATCH_ANY)
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertTrue(actual);
}
@Test
public void testMatchFilenameAndExtensionWithMultiplePeriods() throws Exception {
// Arrange
final String definition = "/foo*.xyzzy.txt";
final String relativePath = "foo.bar.baz.xyzzy.txt";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo"),
new Part(IgnoreLineParser.Token.MATCH_ANY),
new Part(IgnoreLineParser.Token.TEXT, ".xyzzy.txt")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertTrue(actual);
}
@Test
public void testNonMatchFilenameAndExtensionWithMultiplePeriods() throws Exception {
// Arrange
final String definition = "/foo*.xyzzy.txt";
final String relativePath = "foo.bar.baz.qux.txt";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo"),
new Part(IgnoreLineParser.Token.MATCH_ANY),
new Part(IgnoreLineParser.Token.TEXT, ".xyzzy.txt")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertFalse(actual);
}
@Test
public void testMatchWithoutLeadingForwardSlash() throws Exception {
// Arrange
final String definition = "foo*.xyzzy.txt";
final String relativePath = "foo.bar.baz.xyzzy.txt";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "foo"),
new Part(IgnoreLineParser.Token.MATCH_ANY),
new Part(IgnoreLineParser.Token.TEXT, ".xyzzy.txt")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertTrue(actual);
}
@Test
public void testMatchesOnlyRooted() throws Exception {
// Arrange
final String definition = "/path/to/some/foo*.xyzzy.txt";
final String relativePath = "foo.bar.baz.xyzzy.txt";
final List<Part> syntax = Arrays.asList(
new Part(IgnoreLineParser.Token.ROOTED_MARKER),
new Part(IgnoreLineParser.Token.TEXT, "path"),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.TEXT, "to"),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.TEXT, "some"),
new Part(IgnoreLineParser.Token.PATH_DELIM),
new Part(IgnoreLineParser.Token.TEXT, "oo"),
new Part(IgnoreLineParser.Token.MATCH_ANY),
new Part(IgnoreLineParser.Token.TEXT, ".xyzzy.txt")
);
Rule rule = new RootedFileRule(syntax, definition);
Boolean actual = null;
// Act
actual = rule.matches(relativePath);
// Assert
assertFalse(actual);
}
}