-
Notifications
You must be signed in to change notification settings - Fork 130
Path pattern builder #809
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
base: main
Are you sure you want to change the base?
Path pattern builder #809
Changes from all commits
cb49cfa
dd52fb4
1e5be70
8a04af4
c709286
de9fe40
2ee5317
316bb2a
0376f6d
c799e63
f524a6d
5d610a0
1fd3bcd
dc5fd40
da40567
a53edb2
06662c6
986df3a
b460bfe
0cf67c9
9a2b815
67bb127
dbb142c
3f5ab60
33ccb30
96f14ce
469020d
1ff349c
fe03245
6e67360
dac0a50
fe145a0
aff50dd
43aaa0c
09a4e3a
7c87563
49b5d3f
0d85d78
53ebe9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| /* | ||
| * Copyright 2023 LINE Corporation | ||
| * | ||
| * LINE Corporation licenses this file to you under the Apache License, | ||
| * version 2.0 (the "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at: | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
| * License for the specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package com.linecorp.centraldogma.common; | ||
|
|
||
| import static com.google.common.base.Preconditions.checkArgument; | ||
| import static com.google.common.base.Preconditions.checkState; | ||
| import static java.util.Objects.requireNonNull; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Iterator; | ||
| import java.util.List; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| import javax.annotation.Nullable; | ||
|
|
||
| import com.google.common.collect.ImmutableList; | ||
|
|
||
| import com.linecorp.centraldogma.internal.Util; | ||
|
|
||
| /** | ||
| * Builds a new {@link PathPattern}. | ||
| * | ||
| * <h2>Example</h2> | ||
| * <pre>{@code | ||
| * final PathPattern pathPattern = | ||
| * PathPattern.builder() | ||
| * .startsWith("/foo/bar") | ||
| * .contains("/ext") | ||
| * .hasExtension("json") | ||
| * .build(); | ||
| * }</pre> | ||
| */ | ||
| public final class PathPatternBuilder { | ||
|
|
||
| private static final Pattern FILE_EXTENSION_PATTERN = Pattern.compile("^\\.{0,1}[0-9a-zA-Z]+$"); | ||
|
|
||
| @Nullable | ||
| private PathPattern startPattern; | ||
| private final List<PathPattern> innerPatterns = new ArrayList<>(); | ||
| @Nullable | ||
| private PathPattern endPattern; | ||
|
|
||
| PathPatternBuilder() {} | ||
|
|
||
noodlze marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /** | ||
| * Ensures the file name component matches the specified {@code filename}. | ||
| * For example, `endWith("foo.txt")` will match `/foo.txt`, `/alice/foo.txt` and | ||
| * `/alice/bob/foo.txt`, but not `/barfoo.txt`. | ||
| * | ||
| * <p>This option can only be specified once; multiple declarations will override one another. | ||
| * | ||
| * <p>Note: this option and {@link PathPatternBuilder#hasExtension(String)} are mutually exclusive. | ||
| * When both are specified, the latter-most option will override the former. | ||
| */ | ||
| public PathPatternBuilder endsWith(String filename) { | ||
| checkArgument(Util.isValidFileName(filename), "filename"); | ||
| // "/**" is added by the constructor of `DefaultPathPattern` | ||
| endPattern = new DefaultPathPattern(filename); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Ensures the file extension component matches the specified {@code extension}. | ||
| * For example, `hasExtension("json")` will match `mux.json`, `/bar/mux.json` and | ||
| * `/alice/bar/mux.json` but not `/json.txt`. | ||
| * | ||
| * <p>This option can only be specified once; multiple declarations will override one another. | ||
| * | ||
| * <p>Note: this option and {@link PathPatternBuilder#endsWith(String)} are mutually exclusive. | ||
| * When both are specified, the latter-most option will override the former. | ||
| */ | ||
| public PathPatternBuilder hasExtension(String extension) { | ||
| checkArgument(isValidFileExtension(extension), "invalid extension."); | ||
| if (extension.startsWith(".")) { | ||
| endPattern = new DefaultPathPattern("/**/*" + extension); | ||
| } else { // add extension separator | ||
| endPattern = new DefaultPathPattern("/**/*." + extension); | ||
| } | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Ensures the directory path starts with the specified {@code dirPath}. | ||
| * For example, `startsWith("/foo")` will match `/foo/test.zip`, `/foo/bar/test.zip` | ||
| * but not `/nix/foo/test.zip`. | ||
| * | ||
| * <p>This option can only be specified once; multiple declarations will override one another. | ||
| */ | ||
| public PathPatternBuilder startsWith(String dirPath) { | ||
| checkArgument(Util.isValidDirPath(dirPath), "dir"); | ||
| // appends "/**" | ||
| startPattern = new DefaultPathPattern(dirPath + (dirPath.endsWith("/") ? "" : "/") + "**"); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Ensures the directory path contains the specified {@code dirPath}. | ||
| * For example, `contains("/bar")` will match `/nix/bar/test.zip`, `/nix/quix/bar/twee/test.zip` | ||
| * but not `/bar/foo/test.zip` or `/ren/bar.json`. | ||
| * | ||
| * <p>This option can be specified multiple times; multiple declarations will be chained. | ||
| * For example, `contains("/bar").contains("foo")` | ||
| * creates the glob-like pattern string `/**/bar/**/foo/**". | ||
| */ | ||
| public PathPatternBuilder contains(String dirPath) { | ||
| checkArgument(Util.isValidDirPath(dirPath), "dirPath"); | ||
| // Prepends and appends "/**" | ||
| final PathPattern contain = new DefaultPathPattern("/**" + dirPath + | ||
| (dirPath.endsWith("/") ? "" : "/") + "**"); | ||
| innerPatterns.add(contain); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Compose one pathPattern from a list of {@code patterns}. | ||
| */ | ||
| private static String combine(List<PathPattern> patterns) { | ||
| final StringBuilder sb = new StringBuilder(); | ||
| for (final Iterator<PathPattern> i = patterns.iterator(); i.hasNext();) { | ||
| if (sb.length() == 0) { | ||
| // left should end with "/**" | ||
| sb.append(i.next().patternString()); | ||
| } else { | ||
| // right should start with "/**/" | ||
| sb.append(i.next().patternString().substring(3)); | ||
| } | ||
| } | ||
| return sb.toString(); | ||
| } | ||
|
|
||
| /** | ||
| * Returns a newly-created {@link PathPattern} based on the options of this builder. | ||
| */ | ||
| public PathPattern build() { | ||
| final ImmutableList.Builder<PathPattern> optionsBuilder = ImmutableList.builder(); | ||
| if (startPattern != null) { | ||
| optionsBuilder.add(startPattern); | ||
| } | ||
| optionsBuilder.addAll(innerPatterns); | ||
| if (endPattern != null) { | ||
| optionsBuilder.add(endPattern); | ||
| } | ||
| final ImmutableList<PathPattern> options = optionsBuilder.build(); | ||
|
|
||
| checkState(!options.isEmpty(), "Requires at least one pattern to build in PathPatternBuilder"); | ||
|
|
||
| if (options.size() == 1) { | ||
| return options.get(0); | ||
| } | ||
| return new DefaultPathPattern(combine(options)); | ||
| } | ||
|
|
||
| private static boolean isValidFileExtension(String extension) { | ||
| requireNonNull(extension, "extension"); | ||
| checkArgument(!extension.isEmpty(), "extension is empty."); | ||
| return FILE_EXTENSION_PATTERN.matcher(extension).matches(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /* | ||
| * Copyright 2021 LINE Corporation | ||
| * | ||
| * LINE Corporation licenses this file to you under the Apache License, | ||
| * version 2.0 (the "License"); you may not use this file except in compliance | ||
| * with the License. You may obtain a copy of the License at: | ||
| * | ||
| * https://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
| * License for the specific language governing permissions and limitations | ||
| * under the License. | ||
| */ | ||
| package com.linecorp.centraldogma.common; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThatNoException; | ||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class PathPatternBuilderTest { | ||
| @Test | ||
| void testExtensionPattern() { | ||
| assertThatNoException().isThrownBy(() -> PathPattern.builder().hasExtension(".JPG")); | ||
| assertThatNoException().isThrownBy(() -> PathPattern.builder().hasExtension(".7z")); | ||
|
|
||
| assertThatThrownBy(() -> PathPattern.builder().hasExtension("가txt")) | ||
| .isInstanceOf(IllegalArgumentException.class); | ||
| assertThatThrownBy(() -> PathPattern.builder().hasExtension("..tx.t")) | ||
| .isInstanceOf(IllegalArgumentException.class); | ||
| } | ||
|
|
||
| @Test | ||
| void testSingleOption() { | ||
| assertThat(PathPattern.builder().startsWith("/foo/bar").build() | ||
| .patternString()).isEqualTo("/foo/bar/**"); | ||
| assertThat(PathPattern.builder().endsWith("foo.json").build() | ||
| .patternString()).isEqualTo("/**/foo.json"); | ||
| assertThat(PathPattern.builder().contains("/bar").build() | ||
| .patternString()).isEqualTo("/**/bar/**"); | ||
| assertThat(PathPattern.builder().hasExtension("json").build() | ||
| .patternString()).isEqualTo("/**/*.json"); | ||
| } | ||
|
|
||
| @Test | ||
| void testPathPatternBuilder() { | ||
| assertThat(PathPattern.builder() | ||
| .startsWith("/foo/bar") | ||
| .endsWith("foo.txt") | ||
| .build() | ||
| .patternString()).isEqualTo("/foo/bar/**/foo.txt"); | ||
| assertThat(PathPattern.builder() | ||
| .startsWith("/foo") | ||
| .contains("/bar/") | ||
| .hasExtension("json") | ||
| .build() | ||
| .patternString()).isEqualTo("/foo/**/bar/**/*.json"); | ||
|
|
||
| assertThat(PathPattern.builder() | ||
| .startsWith("/foo") | ||
| .endsWith("qux.json") | ||
| .hasExtension("json") | ||
| .build() | ||
| .patternString()).isEqualTo("/foo/**/*.json"); | ||
| assertThat(PathPattern.builder() | ||
| .startsWith("/foo") | ||
| .hasExtension("json") | ||
| .endsWith("qux.json") | ||
| .build() | ||
| .patternString()).isEqualTo("/foo/**/qux.json"); | ||
|
|
||
| assertThat(PathPattern.builder() | ||
| .contains("/foo") | ||
| .contains("/bar") | ||
| .build() | ||
| .patternString()).isEqualTo("/**/foo/**/bar/**"); | ||
| assertThat(PathPattern.builder() | ||
| .startsWith("/foo/bar") | ||
| .startsWith("/override") | ||
| .hasExtension("json") | ||
| .build() | ||
| .patternString()).isEqualTo("/override/**/*.json"); | ||
|
Comment on lines
+75
to
+85
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be quite confusing to users. Should we always override, even for
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @trustin , |
||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.