diff --git a/README.md b/README.md index fde53192975..37c12afe0eb 100644 --- a/README.md +++ b/README.md @@ -408,6 +408,44 @@ java -Dapis -DmodelTests=false {opts} When using selective generation, _only_ the templates needed for the specific generation will be used. +### Ignore file format + +Swagger codegen supports a `.swagger-codegen-ignore` file, similar to `.gitignore` or `.dockerignore` you're probably already familiar with. + +The ignore file allows for better control over overwriting existing files than the `--skip-overwrite` flag. With the ignore file, you can specify individual files or directories can be ignored. This can be useful, for example if you only want a subset of the generated code. + +Examples: + +``` +# Swagger Codegen Ignore +# Lines beginning with a # are comments + +# This should match build.sh located anywhere. +build.sh + +# Matches build.sh in the root +/build.sh + +# Exclude all recursively +docs/** + +# Explicitly allow files excluded by other rules +!docs/UserApi.md + +# Recursively exclude directories named Api +# You can't negate files below this directory. +src/**/Api/ + +# When this file is nested under /Api (excluded above), +# this rule is ignored because parent directory is excluded by previous rule. +!src/**/PetApiTests.cs + +# Exclude a single, nested file explicitly +src/IO.Swagger.Test/Model/AnimalFarmTests.cs +``` + +The `.swagger-codegen-ignore` file must exist in the root of the output directory. + ### Customizing the generator There are different aspects of customizing the code generator beyond just creating or modifying templates. Each language has a supporting configuration file to handle different type mappings, etc: diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/ignore/rules/DirectoryRule.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/ignore/rules/DirectoryRule.java index 2619f1f1c38..235d34d3f96 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/ignore/rules/DirectoryRule.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/ignore/rules/DirectoryRule.java @@ -1,20 +1,27 @@ package io.swagger.codegen.ignore.rules; -import java.nio.file.FileSystems; -import java.nio.file.PathMatcher; +import java.nio.file.*; import java.util.List; public class DirectoryRule extends FileRule { - private PathMatcher matcher = null; + private PathMatcher directoryMatcher = null; + private PathMatcher contentsMatcher = null; DirectoryRule(List syntax, String definition) { super(syntax, definition); - matcher = FileSystems.getDefault().getPathMatcher("glob:**/"+this.getPattern()); + String pattern = this.getPattern(); + StringBuilder sb = new StringBuilder(); + sb.append("glob:"); + sb.append(pattern); + if(!pattern.endsWith("/")) sb.append("/"); + directoryMatcher = FileSystems.getDefault().getPathMatcher(sb.toString()); + sb.append("**"); + contentsMatcher = FileSystems.getDefault().getPathMatcher(sb.toString()); } @Override public Boolean matches(String relativePath) { - return matcher.matches(FileSystems.getDefault().getPath(relativePath)); + return contentsMatcher.matches(FileSystems.getDefault().getPath(relativePath)) || directoryMatcher.matches(FileSystems.getDefault().getPath(relativePath)); } } diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/ignore/rules/IgnoreLineParser.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/ignore/rules/IgnoreLineParser.java index d79bc03ae15..2d3fbdd2994 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/ignore/rules/IgnoreLineParser.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/ignore/rules/IgnoreLineParser.java @@ -78,6 +78,13 @@ public class IgnoreLineParser { i++; continue; } else { + + if (sb.length() > 0) { + // A MATCH_ANY may commonly follow a filename or some other character. Dump that to results before the MATCH_ANY. + parts.add(new Part(Token.TEXT, sb.toString())); + sb.delete(0, sb.length()); + } + parts.add(new Part(Token.MATCH_ANY)); continue; } diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/ignore/CodegenIgnoreProcessorTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/ignore/CodegenIgnoreProcessorTest.java index a9fe89214d5..784fcfe8480 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/ignore/CodegenIgnoreProcessorTest.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/ignore/CodegenIgnoreProcessorTest.java @@ -1,21 +1,141 @@ package io.swagger.codegen.ignore; -import org.testng.annotations.Test; +import org.apache.commons.io.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.*; +import static org.testng.Assert.*; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; public class CodegenIgnoreProcessorTest { - @Test - public void loadCodegenRules() throws Exception { + private static final Logger LOGGER = LoggerFactory.getLogger(CodegenIgnoreProcessorTest.class); + + private Boolean allowed; + private final String filename; + private final String ignoreDefinition; + private final String description; + private String outputDir; + private File target; + private Path temp; + + private CodegenIgnoreProcessorTest(String filename, String ignoreDefinition, String description) throws IOException { + this.filename = filename; + this.ignoreDefinition = ignoreDefinition; + this.description = description; + } + + CodegenIgnoreProcessorTest allowed() { + this.allowed = true; + return this; + } + + CodegenIgnoreProcessorTest ignored() { + this.allowed = false; + return this; + } + + private void prepareTestFiles() throws IOException { + // NOTE: Each test needs its own directory because .swagger-codegen-ignore needs to exist at the root. + temp = Files.createTempDirectory(getClass().getSimpleName()); + this.outputDir = temp.toFile().getAbsolutePath(); + + target = new File(this.outputDir, this.filename); + + boolean mkdirs = target.getParentFile().mkdirs(); + if(!mkdirs) { + LOGGER.warn("Failed to create directories for CodegenIgnoreProcessorTest test file. Directory may already exist."); + } + + Path created = Files.createFile(target.toPath()); + if(!created.toFile().exists()) { + throw new IOException("Failed to write CodegenIgnoreProcessorTest test file."); + } + + // System.out.print(String.format("Created codegen ignore processor test file: %s\n", created.toAbsolutePath())); + File ignoreFile = new File(this.outputDir, ".swagger-codegen-ignore"); + try (FileOutputStream stream = new FileOutputStream(ignoreFile)) { + stream.write(this.ignoreDefinition.getBytes()); + } + } + + @AfterTest + public void afterTest() throws IOException { + if(temp != null && temp.toFile().exists() && temp.toFile().isDirectory()) { + FileUtils.deleteDirectory(temp.toFile()); + } } @Test - public void getInclusionRules() throws Exception { + public void evaluate() { + // Arrange + try { + // Lazily setup files to avoid conflicts and creation when these tests may not even run. + prepareTestFiles(); + } catch (IOException e) { + e.printStackTrace(); + fail("Failed to prepare test files. " + e.getMessage()); + } + CodegenIgnoreProcessor processor = new CodegenIgnoreProcessor(outputDir); + Boolean actual = null; + // Act + actual = processor.allowsFile(target); + + // Assert + assertEquals(actual, this.allowed, this.description); } - @Test - public void getExclusionRules() throws Exception { + @Factory + public static Object[] factoryMethod() throws IOException { + return new Object[] { + // Matching filenames + new CodegenIgnoreProcessorTest("build.sh", "build.sh", "A file when matching should ignore.").ignored(), + new CodegenIgnoreProcessorTest("src/build.sh", "**/build.sh", "A file when matching nested files should ignore.").ignored(), + new CodegenIgnoreProcessorTest("Build.sh", "build.sh", "A file when non-matching should allow.").allowed(), + new CodegenIgnoreProcessorTest("build.sh", "/build.sh", "A rooted file when matching should ignore.").ignored(), + new CodegenIgnoreProcessorTest("nested/build.sh", "/build.sh", "A rooted file definition when non-matching should allow.").allowed(), + new CodegenIgnoreProcessorTest("src/IO.Swagger.Test/Model/AnimalFarmTests.cs", "src/IO.Swagger.Test/Model/AnimalFarmTests.cs", "A file when matching exactly should ignore.").ignored(), + // Matching spaces in filenames + new CodegenIgnoreProcessorTest("src/properly escaped.txt", "**/properly escaped.txt", "A file when matching nested files with spaces in the name should ignore.").ignored(), + new CodegenIgnoreProcessorTest("src/improperly escaped.txt", "**/improperly\\ escaped.txt", "A file when matching nested files with spaces in the name (improperly escaped rule) should allow.").allowed(), + + // Match All + new CodegenIgnoreProcessorTest("docs/somefile.md", "docs/**", "A recursive file (0 level) when matching should ignore.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/somefile.md", "docs/**", "A recursive file (1 level) when matching should ignore.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/2/3/somefile.md", "docs/**", "A recursive file (n level) when matching should ignore.").ignored(), + + // Match Any + new CodegenIgnoreProcessorTest("docs/1/2/3/somefile.md", "docs/**/somefile.*", "A recursive file with match-any extension when matching should ignore.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/2/3/somefile.java", "docs/**/*.java", "A recursive file with match-any file name when matching should ignore.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/2/3/4/somefile.md", "docs/**/*", "A recursive file with match-any file name when matching should ignore.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/2/3/4/5/somefile.md", "docs/**/anyfile.*", "A recursive file with match-any extension when non-matching should allow.").allowed(), + + // Directory matches + new CodegenIgnoreProcessorTest("docs/1/Users/a", "docs/**/Users/", "A directory rule when matching should be ignored.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/Users1/a", "docs/**/Users/", "A directory rule when non-matching should be allowed.").allowed(), + + // Negation of excluded recursive files + new CodegenIgnoreProcessorTest("docs/UserApi.md", "docs/**\n!docs/UserApi.md", "A pattern negating a previous ignore FILE rule should be allowed.").allowed(), + + // Negation of excluded directories + new CodegenIgnoreProcessorTest("docs/1/Users/UserApi.md", "docs/**/Users/\n!docs/1/Users/UserApi.md", "A pattern negating a previous ignore DIRECTORY rule should be ignored.").ignored(), + + // Other matches which may not be parsed for correctness, but are free because of PathMatcher + new CodegenIgnoreProcessorTest("docs/1/2/3/Some99File.md", "**/*[0-9]*", "A file when matching against simple regex patterns when matching should be ignored.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/2/3/SomeFile.md", "**/*.{java,md}", "A file when matching against grouped subpatterns for extension when matching (md) should be ignored.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/2/3/SomeFile.java", "**/*.{java,md}", "A file when matching against grouped subpatterns for extension when matching (java) should be ignored.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/2/3/SomeFile.txt", "**/*.{java,md}", "A file when matching against grouped subpatterns for extension when non-matching should be allowed.").allowed(), + + new CodegenIgnoreProcessorTest("docs/1/2/3/foo.c", "**/*.?", "A file when matching against required single-character extension when matching should be ignored.").ignored(), + new CodegenIgnoreProcessorTest("docs/1/2/3/foo.cc", "**/*.?", "A file when matching against required single-character extension when non-matching should be allowed.").allowed() + + }; } - } \ No newline at end of file