File filtering implementation a'la .gitignore

This commit is contained in:
Rustam Vishnyakov
2018-07-19 16:20:17 +03:00
parent a857324c76
commit 3503f2c509
2 changed files with 205 additions and 0 deletions
@@ -0,0 +1,139 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.formatting.fileSet;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.regex.Pattern;
/**
* Describes a set of files specified by a directory and/or by a file mask.
* <p>
* Examples:
* <ul>
* <li>
* {@code *.test}<br>
* A file ending with .test in any directory
* </li>
* <li>
* {@code /foo/*.test}<br>
* A file with *.test extension immediately under <i>project-dir</i>/foo directory.
* </li>
* <li>
* {@code foo/*.test}<br>
* A file with .test extension if their parent directory is "foo", equivalent to &#47;**&#47;foo&#47;*.test
* </li>
* <li>
* {@code /*.test}
* A file with .test extension directly under project root directory.
* </li>
* </ul>
*/
public class FileSetEntry {
private @Nullable Pattern myPathPattern;
private @Nullable Pattern myFileNamePattern;
public FileSetEntry(@NotNull String spec) {
compileSpec(spec);
}
private void compileSpec(@NotNull String spec) {
String pathSpec = "";
String fileSpec;
int lastSlashPos = spec.lastIndexOf('/');
if (lastSlashPos >= 0) {
fileSpec = spec.substring(lastSlashPos + 1);
pathSpec = spec.substring(0, lastSlashPos + 1);
if (pathSpec.length() > 0 && pathSpec.charAt(0) != '/') {
pathSpec = "/**/" + pathSpec;
}
}
else {
fileSpec = spec;
}
if (!pathSpec.isEmpty()) {
myPathPattern = Pattern.compile(specToRegexp(pathSpec, true));
}
if (!fileSpec.isEmpty()) {
myFileNamePattern = Pattern.compile(specToRegexp(fileSpec, false));
}
}
private static String specToRegexp(@NotNull String spec, boolean isPathSpec) {
StringBuilder sb = new StringBuilder();
char[] chars = spec.toCharArray();
int i = 0;
while (i < chars.length) {
char c = chars[i];
switch (c) {
case '*':
if (isPathSpec && i < chars.length - 1 && chars[i + 1] == '*') {
sb.append("([^/]*/)*");
i++;
if (i < chars.length - 1 && chars[i + 1] == '/') {
i ++;
}
}
else {
sb.append("[^/]*");
}
break;
case '?':
sb.append("[^/]");
break;
default:
if (isRegexSpecialChar(c)) {
sb.append('\\').append(c);
}
else {
sb.append(c);
}
}
i ++;
}
return sb.toString();
}
private static boolean isRegexSpecialChar(char c) {
return "^${}[]().*+-&".indexOf(c) >= 0;
}
/**
* Checks if the file set entry matches the given virtual file.
*
* @param project The current project.
* @param virtualFile The virtual file to check against.
* @return True if there is a match, false otherwise.
*/
public boolean matches(@NotNull Project project, @NotNull VirtualFile virtualFile) {
if (myFileNamePattern == null && myPathPattern == null) return false; // Empty spec matches nothing
String name = virtualFile.getName();
VirtualFile parent = virtualFile.getParent();
String path = getRelativePath(project, parent) + "/";
return patternMatches(myPathPattern, path) && patternMatches(myFileNamePattern, name);
}
@NotNull
private static String getRelativePath(@NotNull Project project, @Nullable VirtualFile parent) {
VirtualFile projectDir = project.getBaseDir();
String projectPath = projectDir.getPath();
if (parent != null) {
String parentPath = parent.getPath();
if (parentPath.startsWith(projectPath)) {
return parentPath.substring(projectPath.length());
}
else {
return parentPath;
}
}
return "";
}
private static boolean patternMatches(@Nullable Pattern pattern, @NotNull String str) {
return pattern == null || pattern.matcher(str).matches();
}
}
@@ -0,0 +1,66 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.formatting.fileSet;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.PlatformTestCase;
import org.jetbrains.annotations.NotNull;
public class FileSetEntryTest extends PlatformTestCase {
public void testSimpleFileMask() {
FileSetEntry entry = new FileSetEntry("*.java");
assertTrue(entry.matches(getProject(), createFile("src/test.java")));
assertFalse(entry.matches(getProject(), createFile("src/test.txt")));
assertTrue(entry.matches(getProject(), createFile("file3.java")));
}
public void testFileMaskWithDir() {
FileSetEntry entry = new FileSetEntry("/src/*.java");
assertTrue(entry.matches(getProject(), createFile("src/test.java")));
assertFalse(entry.matches(getProject(), createFile("src/test.txt")));
assertFalse(entry.matches(getProject(), createFile("file3.java")));
}
public void testExtendedDirSpec() {
FileSetEntry entry = new FileSetEntry("/src/**/test/*.java");
assertTrue(entry.matches(getProject(), createFile("src/test/file1.java")));
assertTrue(entry.matches(getProject(), createFile("src/dir1/test/file1.java")));
assertTrue(entry.matches(getProject(), createFile("src/dir1/dir2/test/file1.java")));
assertFalse(entry.matches(getProject(), createFile("src/dir1/dir2/test/dir3/file1.java")));
FileSetEntry entry2 = new FileSetEntry("/src/**/test/**/*.java");
assertTrue(entry2.matches(getProject(), createFile("src/test/a/file1.java")));
assertTrue(entry2.matches(getProject(), createFile("src/b/test/a/file1.java")));
}
public void testAnyCharMarks() {
FileSetEntry entry = new FileSetEntry("/test-??/**/test?.java");
assertFalse(entry.matches(getProject(), createFile("test-1/test2.java")));
assertTrue(entry.matches(getProject(), createFile("test-12/test3.java")));
assertTrue(entry.matches(getProject(), createFile("test-12/test/testa.java")));
}
public void testRelativeDirTest() {
FileSetEntry entry = new FileSetEntry("test/*.java");
assertTrue(entry.matches(getProject(), createFile("src/test/file1.java")));
assertTrue(entry.matches(getProject(), createFile("test/file2.java")));
assertTrue(entry.matches(getProject(), createFile("a/b/c/d/test/file3.java")));
}
private VirtualFile createFile(@NotNull String path) {
String[] dirNames = path.split("/");
VirtualFile baseDir = getProject().getBaseDir();
for (int i = 0; i < dirNames.length - 1; i ++) {
VirtualFile existing = VfsUtilCore.findRelativeFile(dirNames[i], baseDir);
if (existing == null) {
baseDir = createChildDirectory(baseDir, dirNames[i]);
}
else {
baseDir = existing;
}
}
return createChildData(baseDir, dirNames[dirNames.length - 1]);
}
}