apply(CalendarComponent component) {
56 | try {
57 | replace(component.getProperty(propertyName));
58 | } catch (IOException | URISyntaxException | ParseException e) {
59 | throw new ColanderParserException(e);
60 | }
61 | return Optional.of(component);
62 | }
63 |
64 | /**
65 | * Visible for testing.
66 | */
67 | void replace(Property property) throws IOException, URISyntaxException, ParseException {
68 | if (property == null) {
69 | return;
70 | }
71 | String value = property.getValue();
72 | if (value != null) {
73 | property.setValue(value.replaceAll(regex, stringToReplace));
74 | }
75 | }
76 |
77 | public String getRegex() { return regex; }
78 |
79 | public String getStringToReplace() { return stringToReplace; }
80 |
81 | public String getPropertyName() { return propertyName; }
82 | }
83 |
--------------------------------------------------------------------------------
/cli/src/test/java/info/schnatterer/colander/cli/ColanderCliITCase.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander.cli;
25 |
26 | import info.schnatterer.colander.test.ITCases;
27 | import org.junit.Rule;
28 | import org.junit.Test;
29 | import org.junit.contrib.java.lang.system.ExpectedSystemExit;
30 | import org.junit.rules.TemporaryFolder;
31 |
32 | import java.io.File;
33 |
34 | import static org.junit.Assert.assertTrue;
35 |
36 | /**
37 | * Integration tests that tests colander CLI end-to-end, from ics file to ics file, using
38 | * {@link ColanderCli#main(String[])} method and exit codes.
39 | */
40 | public class ColanderCliITCase {
41 |
42 | @Rule
43 | public TemporaryFolder folder = new TemporaryFolder();
44 |
45 | @Rule
46 | public final ExpectedSystemExit exit = ExpectedSystemExit.none();
47 |
48 | @Test
49 | public void endToEnd() throws Exception {
50 | String inputPath = ITCases.getFilePathTestIcs(folder);
51 | String outputPath = folder.getRoot().toString() + "/out.ics";
52 | exit.expectSystemExitWithStatus(0);
53 | exit.checkAssertionAfterwards(() -> {
54 | assertTrue("Output not written", new File(outputPath).exists());
55 | ITCases.verifyParsedIcs(inputPath, outputPath);
56 | });
57 | execute(
58 | "--remove-duplicate-events",
59 | "--remove-empty-events",
60 | "--remove-summary", "Remove me",
61 | "--remove-description", "Remove me 2",
62 | "--replace-description L.ne=Line",
63 | "--replace-summary Replace=Replace!",
64 | inputPath,
65 | outputPath
66 | );
67 | }
68 |
69 | @Test
70 | public void endToEndParsingArgs() throws Exception {
71 | exit.expectSystemExitWithStatus(1);
72 | execute("--wtf");
73 | }
74 |
75 | @Test
76 | public void endToEndExceptionParsing() throws Exception {
77 | exit.expectSystemExitWithStatus(2);
78 | // Try to overwrite input file
79 | execute(ITCases.getFilePathTestIcs(folder), ITCases.getFilePathTestIcs(folder));
80 | }
81 |
82 | private void execute(String... args) { ColanderCli.main(args); }
83 | }
84 |
--------------------------------------------------------------------------------
/test-lib/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
27 |
30 | 4.0.0
31 |
32 |
33 | info.schnatterer.colander
34 | colander-parent
35 | 0.2.1-SNAPSHOT
36 |
37 |
38 | colander-test-lib
39 | test-lib
40 |
41 | jar
42 |
43 |
44 | ${project.parent.basedir}
45 |
46 |
47 |
48 |
49 | ${project.parent.groupId}
50 | colander-commons-lib
51 | ${project.parent.version}
52 |
53 |
54 |
55 |
56 | org.slf4j
57 | slf4j-api
58 | ${slf4j.version}
59 |
60 |
61 |
62 |
63 | junit
64 | junit
65 | compile
66 |
67 |
68 | org.hamcrest
69 | hamcrest-junit
70 | compile
71 |
72 |
73 |
74 |
75 |
76 |
77 | org.apache.maven.plugins
78 | maven-failsafe-plugin
79 |
80 |
81 | **/ITCasesTest.java
82 |
83 |
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | #
2 | # The MIT License (MIT)
3 | #
4 | # Copyright (c) 2017 Johannes Schnatterer
5 | #
6 | # Permission is hereby granted, free of charge, to any person obtaining a copy
7 | # of this software and associated documentation files (the "Software"), to deal
8 | # in the Software without restriction, including without limitation the rights
9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | # copies of the Software, and to permit persons to whom the Software is
11 | # furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in all
14 | # copies or substantial portions of the Software.
15 | #
16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | # SOFTWARE.
23 | #
24 |
25 | # Define maven version for all stages
26 | # Contains git, in order to be able to write version info during maven build
27 | FROM maven:3.6.1-jdk-11 as maven-git
28 |
29 | FROM maven-git as mavencache
30 | ENV MAVEN_OPTS=-Dmaven.repo.local=/mvn
31 | COPY pom.xml /mvn/
32 | COPY cli/pom.xml /mvn/cli/
33 | COPY commons-lib/pom.xml /mvn/commons-lib/
34 | COPY core/pom.xml /mvn/core/
35 | COPY test-lib/pom.xml /mvn/test-lib/
36 | WORKDIR /mvn
37 | RUN mvn compile dependency:resolve dependency:resolve-plugins # --fail-never
38 |
39 | FROM maven-git as mavenbuild
40 | ARG ADDITIONAL_BUILD_ARG
41 | ENV MAVEN_OPTS=-Dmaven.repo.local=/mvn
42 | COPY . /mvn
43 | COPY --from=mavencache /mvn/ /mvn/
44 | WORKDIR /mvn
45 | RUN set -x && mvn package -Djar ${ADDITIONAL_BUILD_ARG}
46 | RUN rm -rf /mvn/cli/target/colander-cli-*-sources.jar && \
47 | rm -rf /mvn/cli/target/colander-cli-*-javadoc.jar
48 | RUN mv /mvn/cli/target/colander-cli-*.jar /colander.jar
49 |
50 | # Only way to make distroless build deterministic: Use repo digest
51 | # $ docker pull gcr.io/distroless/java:11
52 | # Digest: sha256:da8aa0fa074d0ed9c4b71ad15af5dffdf6afdd768efbe2f0f7b0d60829278630
53 | # $ docker run --rm -ti gcr.io/distroless/java:11 -version
54 | # openjdk version "11.0.2" 2019-01-15
55 | FROM gcr.io/distroless/java@sha256:da8aa0fa074d0ed9c4b71ad15af5dffdf6afdd768efbe2f0f7b0d60829278630
56 | ARG VCS_REF
57 | ARG SOURCE_REPOSITORY_URL
58 | ARG GIT_TAG
59 | ARG BUILD_DATE
60 | # See https://github.com/opencontainers/image-spec/blob/master/annotations.md
61 | LABEL org.opencontainers.image.created="${BUILD_DATE}" \
62 | org.opencontainers.image.authors="schnatterer" \
63 | org.opencontainers.image.url="https://hub.docker.com/r/schnatterer/colander/" \
64 | org.opencontainers.image.documentation="https://hub.docker.com/r/schnatterer/colander/" \
65 | org.opencontainers.image.source="${SOURCE_REPOSITORY_URL}" \
66 | org.opencontainers.image.version="${GIT_TAG}" \
67 | org.opencontainers.image.revision="${VCS_REF}" \
68 | org.opencontainers.image.vendor="schnatterer" \
69 | org.opencontainers.image.licenses="MIT" \
70 | org.opencontainers.image.title="colander" \
71 | org.opencontainers.image.description="colander - filtering your calendar"
72 |
73 | COPY --from=mavenbuild /colander.jar /app/colander.jar
74 | ENTRYPOINT ["java", "-jar", "/app/colander.jar"]
75 |
--------------------------------------------------------------------------------
/core/src/test/java/info/schnatterer/colander/RemoveEmptyEventFilterTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.model.Date;
27 | import net.fortuna.ical4j.model.component.VEvent;
28 | import net.fortuna.ical4j.model.property.Description;
29 | import org.junit.Test;
30 |
31 | import static org.assertj.core.api.Assertions.assertThat;
32 | import static org.junit.Assert.assertSame;
33 |
34 | public class RemoveEmptyEventFilterTest {
35 | private RemoveEmptyEventFilter filter = new RemoveEmptyEventFilter();
36 |
37 | @Test
38 | public void applyMatch() throws Exception {
39 | VEvent event = new VEvent(false);
40 | assertThat(filter.apply(event)).isEmpty();
41 | }
42 |
43 | @Test
44 | public void applyMatchEmptyStrings() throws Exception {
45 | VEvent event = new VEvent(new Date(), "");
46 | event.getProperties().add(new Description(""));
47 | assertThat(filter.apply(event)).isEmpty();
48 | }
49 |
50 | @Test
51 | public void applyMatchNull() throws Exception {
52 | VEvent event = new VEvent(new Date(), null);
53 | event.getProperties().add(new Description(null));
54 | assertThat(filter.apply(event)).isEmpty();
55 | }
56 |
57 | @Test
58 | public void applyDescriptionEmpty() throws Exception {
59 | VEvent event = new VEvent(new Date(), "sumry");
60 | assertSame("Unexpected filtering result", event, filter.apply(event).orElse(null));
61 | }
62 |
63 | @Test
64 | public void applySummaryEmpty() throws Exception {
65 | VEvent event = new VEvent(false);
66 | event.getProperties().add(new Description("descr"));
67 | assertSame("Unexpected filtering result", event, filter.apply(event).orElse(null));
68 | }
69 |
70 | @Test
71 | public void applySummaryNull() throws Exception {
72 | VEvent event = new VEvent(new Date(), null);
73 | event.getProperties().add(new Description("desc"));
74 | assertSame("Unexpected filtering result", event, filter.apply(event).orElse(null));
75 | }
76 |
77 | @Test
78 | public void applyDescriptionNull() throws Exception {
79 | VEvent event = new VEvent(new Date(), "sumry");
80 | event.getProperties().add(new Description(null));
81 | assertSame("Unexpected filtering result", event, filter.apply(event).orElse(null));
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/commons-lib/src/test/java/info/schnatterer/colander/PropertiesTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.model.component.CalendarComponent;
27 | import net.fortuna.ical4j.model.component.VEvent;
28 | import net.fortuna.ical4j.model.property.Description;
29 | import net.fortuna.ical4j.model.property.Summary;
30 | import org.junit.Assert;
31 | import org.junit.Test;
32 |
33 | import java.util.Optional;
34 |
35 | public class PropertiesTest {
36 | CalendarComponent calendarComponent = new VEvent();
37 |
38 | @Test
39 | public void getProperty() throws Exception {
40 | Summary expectedSummary = new Summary("value");
41 | calendarComponent.getProperties().add(expectedSummary);
42 | Assert.assertEquals(expectedSummary, Properties.getSummary(calendarComponent).orElse(null));
43 | }
44 |
45 | @Test
46 | public void getPropertyNoSummary() throws Exception {
47 | Assert.assertEquals(Optional.empty(), Properties.getSummary(calendarComponent));
48 | }
49 |
50 | @Test
51 | public void getPropertyValue() throws Exception {
52 | String expectedValue = "val";
53 | calendarComponent.getProperties().add(new Summary(expectedValue));
54 | Assert.assertEquals(expectedValue, Properties.getSummaryValue(calendarComponent).orElse(null));
55 | }
56 |
57 | @Test
58 | public void getPropertyValueNoSummary() throws Exception {
59 | Assert.assertEquals(Optional.empty(), Properties.getSummaryValue(calendarComponent));
60 | }
61 |
62 | @Test
63 | public void getPropertyValueNoSummaryValue() throws Exception {
64 | Summary expectedSummary = new Summary(null);
65 | calendarComponent.getProperties().add(expectedSummary);
66 | Assert.assertEquals(Optional.empty(), Properties.getSummaryValue(calendarComponent));
67 | }
68 |
69 | @Test
70 | public void getDescription() throws Exception {
71 | Description expectedDescription = new Description("value");
72 | calendarComponent.getProperties().add(expectedDescription);
73 | Assert.assertEquals(expectedDescription, Properties.getDescription(calendarComponent).orElse(null));
74 | }
75 |
76 | @Test
77 | public void getDescriptionValue() throws Exception {
78 | String expectedValue = "val";
79 | calendarComponent.getProperties().add(new Description(expectedValue));
80 | Assert.assertEquals(expectedValue, Properties.getDescriptionValue(calendarComponent).orElse(null));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/cli/src/main/java/info/schnatterer/colander/cli/ArgumentsParser.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander.cli;
25 |
26 | import com.beust.jcommander.JCommander;
27 | import com.beust.jcommander.ParameterException;
28 | import org.slf4j.Logger;
29 | import org.slf4j.LoggerFactory;
30 |
31 | /**
32 | * Parser for CLI-arguments
33 | */
34 | class ArgumentsParser {
35 | private static final Logger LOG = LoggerFactory.getLogger(ArgumentsParser.class);
36 |
37 | /** Use {@link #read(String[], String)} instead of constructor. */
38 | ArgumentsParser() {
39 | }
40 |
41 | /**
42 | * Reads the command line parameters and prints error messages when something went wrong.
43 | *
44 | * @param argv arguments passed via CLI
45 | * @return an instance of {@link Arguments}
46 | * @throws ArgumentException on syntax error
47 | */
48 | @SuppressWarnings("squid:S2629") // Log statements are used for console output
49 | public static Arguments read(String[] argv, String programName) {
50 | Arguments arguments = new Arguments();
51 | JCommander commander = new JCommander(arguments);
52 | try {
53 | commander.setProgramName(programName);
54 | commander.parse(argv);
55 | } catch (ParameterException e) {
56 | // Print error and usage
57 | String usage = createUsage(e.getMessage() + System.lineSeparator(), commander);
58 | LOG.error(usage);
59 | // Rethrow, so the main application knows something went wrong
60 | throw new ArgumentException(usage, e);
61 | }
62 |
63 | if (arguments.isHelp()) {
64 | LOG.info(createUsage("", commander));
65 | }
66 |
67 | return arguments;
68 | }
69 |
70 | /**
71 | * Creates a usage string to be displayed on console.
72 | *
73 | * @param prefix written before usage
74 | * @return the usage string
75 | */
76 | private static String createUsage(String prefix, JCommander commander) {
77 | StringBuilder usage = new StringBuilder(prefix);
78 | commander.usage(usage, " ");
79 | return usage.toString();
80 | }
81 |
82 |
83 | /**
84 | * Exception thrown when parameter syntax is invalid.
85 | */
86 | static class ArgumentException extends RuntimeException {
87 | /**
88 | * Constructs a new runtime exception with the specified detail message and cause.
89 | * Note that the detail message associated with {@code cause} is not automatically incorporated in
90 | * this runtime exception's detail message.
91 | *
92 | * @param message the detail message (which is saved for later retrieval by the {@link #getMessage()} method).
93 | * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method).
94 | * (A null value is permitted, and indicates that the cause is nonexistent or unknown.)
95 | */
96 | ArgumentException(String message, Throwable cause) {
97 | super(message, cause);
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/core/src/main/java/info/schnatterer/colander/FilterChain.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.model.Calendar;
27 | import net.fortuna.ical4j.model.ComponentList;
28 | import net.fortuna.ical4j.model.component.CalendarComponent;
29 | import net.fortuna.ical4j.util.CompatibilityHints;
30 | import org.slf4j.Logger;
31 | import org.slf4j.LoggerFactory;
32 |
33 | import java.util.List;
34 | import java.util.Optional;
35 | import java.util.concurrent.atomic.AtomicInteger;
36 |
37 | /**
38 | * Brings together multiple {@link ColanderFilter}s and applies them to all events of an iCal file.
39 | */
40 | class FilterChain {
41 | static {
42 | CompatibilityHints.setHintEnabled(CompatibilityHints.KEY_RELAXED_VALIDATION, true);
43 | }
44 |
45 | private static final Logger LOG = LoggerFactory.getLogger(FilterChain.class);
46 |
47 | private final List filters;
48 |
49 | public FilterChain(List filters) {
50 | this.filters = filters;
51 | }
52 |
53 | /**
54 | * Applies all filters of the chain to an iCal file.
55 | *
56 | * @param cal the iCal to parse
57 | * @return the modified iCal, never {@code null}
58 | */
59 | Calendar run(Calendar cal) {
60 | LOG.info("Start processing. Please wait...");
61 | AtomicInteger changedComponents = new AtomicInteger(0);
62 | // Create empty output calendar with same properties
63 | Calendar calOut = new Calendar(cal.getProperties(), new ComponentList<>());
64 |
65 | ComponentList allComponents = cal.getComponents();
66 | for (CalendarComponent component : allComponents) {
67 | int originalHashCode = component.hashCode();
68 | filterEvent(component).ifPresent( filteredComponent -> {
69 | if (originalHashCode != filteredComponent.hashCode()) {
70 | changedComponents.incrementAndGet();
71 | }
72 | calOut.getComponents().add(filteredComponent);
73 | });
74 | }
75 | LOG.info("Number of components processed: {}", allComponents.size());
76 | LOG.info("Number of components in new calendar: {}", calOut.getComponents().size());
77 | LOG.info("Number of components deleted: {}", allComponents.size() - calOut.getComponents().size());
78 | LOG.info("Number of components changed during filtering: {}", changedComponents.get());
79 |
80 | return calOut;
81 | }
82 |
83 | /**
84 | * Visible for testing
85 | * @param component
86 | */
87 | @SuppressWarnings("WeakerAccess")
88 | protected Optional filterEvent(CalendarComponent component) {
89 | CalendarComponent filteredComponent = component;
90 | for (ColanderFilter filter : filters) {
91 | Optional returnedEvent = filter.apply(filteredComponent);
92 | if (returnedEvent.isPresent()) {
93 | filteredComponent = returnedEvent.get();
94 | } else {
95 | LOG.debug("Filter {} deleted originalEvent {}. Properties={}", filter, component.getName(),
96 | component.getProperties());
97 | return Optional.empty();
98 | }
99 | }
100 | return Optional.of(filteredComponent);
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/core/src/main/java/info/schnatterer/colander/RemoveDuplicateEventFilter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.model.component.CalendarComponent;
27 | import net.fortuna.ical4j.model.component.VEvent;
28 | import net.fortuna.ical4j.model.property.Description;
29 | import net.fortuna.ical4j.model.property.DtEnd;
30 | import net.fortuna.ical4j.model.property.DtStart;
31 | import net.fortuna.ical4j.model.property.Summary;
32 |
33 | import java.util.HashSet;
34 | import java.util.Optional;
35 | import java.util.Set;
36 |
37 | /**
38 | * Remove event when summary, description, start date or end date are the same in another event.
39 | */
40 | public class RemoveDuplicateEventFilter extends TypedColanderFilter{
41 |
42 | private Set filteredEvents = new HashSet<>();
43 |
44 | @Override
45 | public Optional applyTyped(VEvent event) {
46 | ComparisonVEvent comparisonVEvent = new ComparisonVEvent(event);
47 |
48 | if (filteredEvents.contains(comparisonVEvent)) {
49 | return Optional.empty();
50 | } else {
51 | filteredEvents.add(comparisonVEvent);
52 | return Optional.of(event);
53 | }
54 | }
55 |
56 |
57 | /**
58 | * Class that specifies the attributes of a {@link VEvent} that are compared when looking for "duplicates".
59 | */
60 | private static class ComparisonVEvent extends VEvent {
61 | private Summary summary;
62 | private Description description;
63 | private DtStart startDate;
64 | private DtEnd endDate;
65 |
66 | ComparisonVEvent(VEvent eventToCompare) {
67 | this.summary = eventToCompare.getSummary();
68 | this.description = eventToCompare.getDescription();
69 | this.startDate = eventToCompare.getStartDate();
70 | this.endDate = eventToCompare.getEndDate();
71 | }
72 |
73 | @Override
74 | public boolean equals(Object o) {
75 | if (this == o) {
76 | return true;
77 | }
78 | if (o == null || getClass() != o.getClass()) {
79 | return false;
80 | }
81 | if (!super.equals(o)) {
82 | return false;
83 | }
84 |
85 | ComparisonVEvent that = (ComparisonVEvent) o;
86 |
87 | if (summary != null ? !summary.equals(that.summary) : that.summary != null) {
88 | return false;
89 | }
90 | if (description != null ? !description.equals(that.description) : that.description != null) {
91 | return false;
92 | }
93 | //noinspection SimplifiableIfStatement
94 | if (startDate != null ? !startDate.equals(that.startDate) : that.startDate != null) {
95 | return false;
96 | }
97 | return endDate != null ? endDate.equals(that.endDate) : that.endDate == null;
98 | }
99 |
100 | @Override
101 | public int hashCode() {
102 | int result = super.hashCode();
103 | result = 31 * result + (summary != null ? summary.hashCode() : 0);
104 | result = 31 * result + (description != null ? description.hashCode() : 0);
105 | result = 31 * result + (startDate != null ? startDate.hashCode() : 0);
106 | result = 31 * result + (endDate != null ? endDate.hashCode() : 0);
107 | return result;
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/cli/src/main/java/info/schnatterer/colander/cli/ColanderCli.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander.cli;
25 |
26 | import com.cloudogu.versionname.VersionName;
27 | import info.schnatterer.colander.Colander;
28 | import info.schnatterer.colander.cli.ArgumentsParser.ArgumentException;
29 | import org.slf4j.Logger;
30 | import org.slf4j.LoggerFactory;
31 |
32 | /**
33 | * Main class of Command Line Interface for colander.
34 | */
35 | @VersionName
36 | class ColanderCli {
37 | private static final String PROGRAM_NAME = "colander";
38 | private static final Logger LOG = LoggerFactory.getLogger(ColanderCli.class);
39 |
40 | /**
41 | * Main class should not be instantiated directly, only via {@link #main(String[])}.
42 | * Visible for testing.
43 | */
44 | ColanderCli() {
45 | }
46 |
47 | /**
48 | * Entry point of the application
49 | *
50 | * @param args arguments passed via CLI
51 | */
52 | public static void main(String[] args) {
53 | System.exit(new ColanderCli().execute(args).getExitStatus());
54 | }
55 |
56 | /**
57 | * Parses {@code args} and starts {@link Colander}.
58 | *
59 | * @param args comand line args to parse.
60 | * @return an exit status to be returned to CLI.
61 | */
62 | @SuppressWarnings({"squid:S1166", // Exceptions are logged in ArgumentsParser by contract.
63 | "squid:S2629" // Log statements are used for console output
64 | })
65 | ExitStatus execute(String[] args) {
66 | LOG.info(createProgramNameWithVersion());
67 | Arguments cliParams;
68 | try {
69 | cliParams = ArgumentsParser.read(args, PROGRAM_NAME);
70 | } catch (ArgumentException e) {
71 | return ExitStatus.ERROR_ARGS;
72 | }
73 |
74 | if (!cliParams.isHelp()) {
75 | return startColander(cliParams);
76 | }
77 | return ExitStatus.SUCCESS;
78 | }
79 |
80 | private String createProgramNameWithVersion() {
81 | return PROGRAM_NAME + " " + Version.NAME;
82 | }
83 |
84 | /**
85 | * Converts an {@link Arguments} object to a {@link Colander} and rinses.
86 | *
87 | * @param args comand line args to parse.
88 | * @return an exit status to be returned to CLI.
89 | */
90 | ExitStatus startColander(Arguments args) {
91 | LOG.debug("CLI arguments={}", args);
92 | Colander.ColanderBuilder colander = createColanderBuilder(args.getInputFile());
93 | if (args.isRemoveDuplicateEvents()) {
94 | colander.removeDuplicateEvents();
95 | }
96 | if (args.isRemoveEmptyEvents()) {
97 | colander.removeEmptyEvents();
98 | }
99 | args.getReplaceInSummary().forEach(colander::replaceInSummary);
100 | args.getReplaceInDescription().forEach(colander::replaceInDescription);
101 | args.getRemoveSummaryContains().forEach(colander::removeSummaryContains);
102 | args.getRemoveDescriptionContains().forEach(colander::removeDescriptionContains);
103 |
104 | try {
105 | colander.rinse().toFile(args.getOutputFile());
106 | } catch (Exception e) {
107 | LOG.error("Error while parsing or writing calender: " + e.getMessage(), e);
108 | return ExitStatus.ERROR_PARSING;
109 | }
110 | return ExitStatus.SUCCESS;
111 | }
112 |
113 | /**
114 | * Visible for testing
115 | */
116 | Colander.ColanderBuilder createColanderBuilder(String inputFile) {
117 | return Colander.toss(inputFile);
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/core/src/test/java/info/schnatterer/colander/ReplaceFilterTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.model.Date;
27 | import net.fortuna.ical4j.model.Property;
28 | import net.fortuna.ical4j.model.component.VEvent;
29 | import net.fortuna.ical4j.model.property.Description;
30 | import net.fortuna.ical4j.model.property.DtStart;
31 | import org.hamcrest.junit.ExpectedException;
32 | import org.junit.Rule;
33 | import org.junit.Test;
34 |
35 | import java.io.IOException;
36 | import java.net.URISyntaxException;
37 | import java.text.ParseException;
38 |
39 | import static org.assertj.core.api.Assertions.assertThat;
40 | import static org.mockito.Mockito.*;
41 |
42 | public class ReplaceFilterTest {
43 | private Date expectedDate = new Date();
44 |
45 | @Rule
46 | public ExpectedException expectedException = ExpectedException.none();
47 |
48 | @Test
49 | public void filterChangesOnMatch() throws Exception {
50 | ReplaceFilter filter = new ReplaceFilter("h.*llo", "hullo", Property.SUMMARY);
51 | VEvent event = new VEvent(expectedDate, "hallo icaltools");
52 | VEvent expectedEvent = new VEvent(expectedDate, "hullo icaltools");
53 | assertThat(filter.apply(event)).hasValue(expectedEvent);
54 | }
55 |
56 | @Test
57 | public void filterIgnoresWhenNoMatch() throws Exception {
58 | ReplaceFilter filter = new ReplaceFilter("hallo", "hullo", Property.SUMMARY);
59 | VEvent event = new VEvent(new Date(), "hullo icaltools");
60 | assertThat(filter.apply(event)).hasValueSatisfying(actual -> assertThat(actual).isSameAs(event));
61 | }
62 |
63 | @Test
64 | public void filterDescription() throws Exception {
65 | ReplaceFilter filter = new ReplaceFilter("h.*llo", "hullo", Property.DESCRIPTION);
66 | VEvent event = createVEvent(expectedDate, "hallo icaltools");
67 | VEvent expectedEvent = createVEvent(expectedDate, "hullo icaltools");
68 | assertThat(filter.apply(event)).hasValue(expectedEvent);
69 | }
70 |
71 | @Test
72 | public void filterPropertyDoesNotExist() throws Exception {
73 | ReplaceFilter filter = new ReplaceFilter("hallo", "hullo", Property.DESCRIPTION);
74 | VEvent event = new VEvent(expectedDate, "hullo icaltools");
75 | // Unfiltered
76 | assertThat(filter.apply(event)).hasValue(event);
77 | }
78 | @Test
79 | public void filterPropertyDoesHaveValue() throws Exception {
80 | ReplaceFilter filter = new ReplaceFilter("hallo", "hullo", Property.SUMMARY);
81 | VEvent event = new VEvent(expectedDate, null);
82 | // Unfiltered
83 | assertThat(filter.apply(event)).hasValue(event);
84 | }
85 |
86 | @Test
87 | public void filterIOException() throws Exception {
88 | testException(new IOException("mocked Message"));
89 | }
90 |
91 | @Test
92 | public void filterURISyntaxException() throws Exception {
93 | testException(new URISyntaxException("uri", "mocked Message"));
94 | }
95 |
96 | @Test
97 | public void filterParseException() throws Exception {
98 | testException(new ParseException("mocked Message", 42));
99 | }
100 |
101 | private VEvent createVEvent(Date startDate, String description) throws IOException, URISyntaxException, ParseException {
102 | VEvent event = new VEvent();
103 | event.getProperties().add(new DtStart(startDate));
104 | event.getProperties().add(new Description(description));
105 | return event;
106 | }
107 |
108 | private void testException(Exception exception) throws IOException, URISyntaxException, ParseException {
109 | String expectedMessage = exception.getMessage();
110 | ReplaceFilter filter = spy(new ReplaceFilter("", "", Property.SUMMARY));
111 | doThrow(exception).when(filter).replace(any());
112 |
113 | expectedException.expect(ColanderParserException.class);
114 | expectedException.expectMessage(expectedMessage);
115 |
116 | filter.apply(new VEvent(false));
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/core/src/test/java/info/schnatterer/colander/RemoveDuplicateEventFilterTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.model.Date;
27 | import net.fortuna.ical4j.model.component.VEvent;
28 | import net.fortuna.ical4j.model.property.Description;
29 | import net.fortuna.ical4j.model.property.DtEnd;
30 | import net.fortuna.ical4j.model.property.Summary;
31 | import org.junit.Before;
32 | import org.junit.Test;
33 |
34 | import java.time.LocalDateTime;
35 | import java.time.Month;
36 | import java.time.ZoneId;
37 |
38 | import static org.assertj.core.api.Assertions.assertThat;
39 |
40 |
41 | public class RemoveDuplicateEventFilterTest {
42 | private RemoveDuplicateEventFilter filter = new RemoveDuplicateEventFilter();
43 |
44 | private LocalDateTime startDate = LocalDateTime.of(2012, Month.DECEMBER, 12, 13, 0);
45 | private LocalDateTime endDate = LocalDateTime.of(2012, Month.DECEMBER, 12, 23, 59);
46 | private LocalDateTime differentDate = LocalDateTime.of(2016, Month.JANUARY, 15, 12, 5);
47 |
48 | private VEvent event = createVEvent("Sum", "descr", startDate, endDate);
49 |
50 | @Before
51 | public void before() {
52 | // Initialize with one event
53 | assertThat(filter.apply(event)).hasValueSatisfying(actual -> assertThat(actual).isSameAs(event));
54 | }
55 |
56 | @Test
57 | public void filterSameEvent() throws Exception {
58 | VEvent sameEvent = event;
59 |
60 | assertThat(filter.apply(event)).isEmpty();
61 | assertThat(filter.apply(sameEvent)).isEmpty();
62 | }
63 |
64 | @Test
65 | public void filterEqualEvent() throws Exception {
66 | VEvent equalEvent =
67 | new VEvent(event.getStartDate().getDate(), event.getEndDate().getDate(), event.getSummary().getValue());
68 | equalEvent.getProperties().add(event.getDescription());
69 |
70 | assertThat(filter.apply(equalEvent)).isEmpty();
71 | }
72 |
73 | @Test
74 | public void filterDifferentSummary() throws Exception {
75 | VEvent differentSummary = createVEvent("DifferentSummary", "descr", startDate, endDate);
76 |
77 | assertThat(filter.apply(differentSummary)).hasValue(differentSummary);
78 | }
79 |
80 | @Test
81 | public void filterDifferentDescription() throws Exception {
82 | VEvent differentSummary = createVEvent("Sum", "DifferentDescr", startDate, endDate);
83 |
84 | assertThat(filter.apply(differentSummary)).hasValue(differentSummary);
85 | }
86 |
87 | @Test
88 | public void filterDifferentStartDate() throws Exception {
89 | VEvent differentStartDate = createVEvent("Sum", "descr", differentDate, endDate);
90 |
91 | assertThat(filter.apply(differentStartDate)).hasValue(differentStartDate);
92 | }
93 |
94 | @Test
95 | public void filterDifferentEndDate() throws Exception {
96 | VEvent differentEndDate = createVEvent("Sum", "descr", startDate, differentDate);
97 |
98 | assertThat(filter.apply(differentEndDate)).hasValue(differentEndDate);
99 | }
100 |
101 | @Test
102 | public void filterEndDateNull() throws Exception {
103 | VEvent endDateNull = new VEvent(toDate(startDate),"end date null");
104 |
105 | assertThat(filter.apply(endDateNull)).hasValue(endDateNull);
106 | }
107 |
108 | @Test
109 | public void filterStartDateNull() throws Exception {
110 | VEvent startDateNull = new VEvent();
111 | startDateNull.getProperties().add(new DtEnd(toDate(endDate)));
112 | startDateNull.getProperties().add(new Summary("start date null"));
113 |
114 | assertThat(filter.apply(startDateNull)).hasValue(startDateNull);
115 | }
116 |
117 | private VEvent createVEvent(String sum, String descr, LocalDateTime startDate, LocalDateTime endDate) {
118 | VEvent event = new VEvent(toDate(startDate), toDate(endDate), sum);
119 | event.getProperties().add(new Description(descr));
120 | return event;
121 | }
122 |
123 | private Date toDate(LocalDateTime of) {
124 | return new Date(java.util.Date.from(of.atZone(ZoneId.systemDefault()).toInstant()));
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/cli/src/test/java/info/schnatterer/colander/cli/ColanderCliTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander.cli;
25 |
26 | import info.schnatterer.colander.Colander;
27 | import org.junit.Before;
28 | import org.junit.Test;
29 | import org.junit.runner.RunWith;
30 | import org.mockito.Mock;
31 | import org.mockito.Spy;
32 | import org.mockito.junit.MockitoJUnitRunner;
33 |
34 | import java.util.Arrays;
35 | import java.util.HashMap;
36 |
37 | import static org.junit.Assert.assertEquals;
38 | import static org.mockito.Mockito.*;
39 |
40 | @RunWith(MockitoJUnitRunner.class)
41 | public class ColanderCliTest {
42 |
43 | @Spy
44 | private ColanderCli cli;
45 |
46 | @Mock
47 | private Colander.ColanderBuilder builder;
48 |
49 | @Mock
50 | private Colander.ColanderResult result;
51 |
52 | @Mock
53 | private Arguments args;
54 |
55 | @Before
56 | public void before() throws Exception {
57 | doReturn(builder).when(cli).createColanderBuilder(any());
58 | when(builder.rinse()).thenReturn(result);
59 | }
60 |
61 | @Test
62 | public void execute() throws Exception {
63 | assertEquals("Exit status", ExitStatus.SUCCESS, execute("a/b.ical"));
64 | }
65 |
66 | @Test
67 | public void executeHelp() throws Exception {
68 | assertEquals("Exit status", ExitStatus.SUCCESS, execute("--help"));
69 | verifyZeroInteractions(builder, result);
70 | }
71 |
72 | @Test
73 | public void executeErrorArgs() throws Exception {
74 | assertEquals("Exit status", ExitStatus.ERROR_ARGS, execute("--wtf"));
75 | }
76 |
77 | @Test
78 | public void executeErrorParsing() throws Exception {
79 | when(builder.rinse()).thenThrow(new RuntimeException("Mocked exception"));
80 | assertEquals("Exit status", ExitStatus.ERROR_PARSING, execute("a/b.ical"));
81 | }
82 |
83 | @Test
84 | public void startColander() throws Exception {
85 | String expectedInput = "in";
86 | String expectedOutput = "out";
87 | when(args.getInputFile()).thenReturn(expectedInput);
88 | when(args.getOutputFile()).thenReturn(expectedOutput);
89 | when(args.isRemoveDuplicateEvents()).thenReturn(true);
90 | when(args.isRemoveEmptyEvents()).thenReturn(true);
91 | when(args.getRemoveSummaryContains()).thenReturn(Arrays.asList("a", "b"));
92 | when(args.getRemoveDescriptionContains()).thenReturn(Arrays.asList("y", "z"));
93 | when(args.getReplaceInSummary()).thenReturn(new HashMap() {{
94 | put("a", "b");
95 | put("c", "d");
96 | }});
97 | when(args.getReplaceInDescription()).thenReturn(new HashMap() {{
98 | put("1", "2");
99 | put("3", "4");
100 | }});
101 |
102 | assertEquals("Exit status", ExitStatus.SUCCESS, cli.startColander(args));
103 |
104 | verify(cli).createColanderBuilder(expectedInput);
105 | verify(builder).removeDuplicateEvents();
106 | verify(builder).removeEmptyEvents();
107 | verify(builder).replaceInSummary("a", "b");
108 | verify(builder).replaceInSummary("c", "d");
109 | verify(builder).replaceInDescription("1", "2");
110 | verify(builder).replaceInDescription("3", "4");
111 | verify(builder).removeSummaryContains("a");
112 | verify(builder).removeSummaryContains("b");
113 | verify(builder).removeDescriptionContains("y");
114 | verify(builder).removeDescriptionContains("z");
115 | verify(result).toFile(expectedOutput);
116 | }
117 |
118 | @Test
119 | public void startColanderEmptyArgs() throws Exception {
120 | assertEquals("Exit status", ExitStatus.SUCCESS, cli.startColander(args));
121 |
122 | verify(cli).createColanderBuilder(null);
123 | verify(builder, never()).removeDuplicateEvents();
124 | verify(builder, never()).removeEmptyEvents();
125 | verify(builder, never()).replaceInSummary(anyString(), anyString());
126 | verify(builder, never()).removeSummaryContains(anyString());
127 | verify(builder, never()).removeDescriptionContains(anyString());
128 | verify(result).toFile(null);
129 | }
130 |
131 | private ExitStatus execute(String... args) {
132 | return cli.execute(args);
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📆 colander - filtering your calendar
2 |
3 | [](https://hub.docker.com/r/schnatterer/colander/)
4 | [](https://travis-ci.org/schnatterer/colander)
5 | [](https://sonarcloud.io/dashboard?id=info.schnatterer.colander%3Acolander-parent)
6 | [](https://sonarcloud.io/dashboard?id=info.schnatterer.colander%3Acolander-parent)
7 | [](https://sonarcloud.io/dashboard?id=info.schnatterer.colander%3Acolander-parent)
8 | [](https://www.jitpack.io/#schnatterer/colander)
9 | [](LICENSE)
10 |
11 | Colander filters calender in ICS files. It can either be used as standalone application via [command line interface](#cli) or within
12 | JVM applications using the [API](#api).
13 |
14 | # CLI
15 |
16 | * Download the latest version from [Releases](https://github.com/schnatterer/colander/releases).
17 | * Extract the zip file.
18 | * Use ist as follows:
19 | ```
20 | Usage: colander [options] [
21 | Options:
22 | --help
23 | (optional) Show this message
24 | Default: false
25 | --remove-description
26 | Remove calender component when description contains expression
27 | Default: []
28 | --remove-duplicate-events
29 | Remove event when summary, description, start date or end date are the
30 | same in another event
31 | Default: false
32 | --remove-empty-events
33 | Remove events when summary and description are empty
34 | Default: false
35 | --remove-summary
36 | Remove calender component when summary contains expression
37 | Default: []
38 | --replace-description
39 | Replace in description of calender components (regex)
40 | Syntax: --replace-descriptionkey=value
41 | Default: {}
42 | --replace-summary
43 | Replace in summary calender components (regex)
44 | Syntax: --replace-summarykey=value
45 | Default: {}
46 | ```
47 | * Example
48 | ```
49 | colander --remove-summary "Remove, RemoveIncludingLeadingSpace" --remove-summary "Another One to remove" --replace-summary "l.ne=line" cal.ics cal-new.ics
50 | ```
51 | * Note that
52 | * filters might refer to specific calender components (such as events). If not otherwise noted, a filter applies to all calender components (tasks, ToDos, Alarms, Venues, etc.)
53 | * the order of the arguments/filters is not maintained. That is, they are not applied in the order as passed
54 | to the CLI.
55 | * If no `output.ics` file is passed, colander creates one, basing on the file name and the current timestamp, e.g. `input-20170129194742.ics`.
56 | * Colander never overwrites existing files. If the `output.ics` exists, colander fails.
57 | * If you care about return codes, they can be found here: [ExitStatus](cli/src/main/java/info/schnatterer/colander/cli/ExitStatus.java))
58 | * Another example is the integration test for CLI (see [ColanderCliITCase](cli/src/test/java/info/schnatterer/colander/cli/ColanderCliITCase.java)).
59 | * Colander CLI writes logs to the `logs` folder.
60 |
61 | # API
62 |
63 | The basic logic of colander is wrapped in the core module. This can be reused in other applications.
64 | For now, this is not hosted on maven central, but on your can get it via jitpack.
65 |
66 | Add the following maven repository to your POM.xml
67 |
68 | ```xml
69 |
70 |
71 | jitpack.io
72 | https://jitpack.io
73 |
74 |
75 | ```
76 |
77 | Then add the actual dependency
78 |
79 | ```xml
80 |
81 | com.github.schnatterer.colander
82 | colander-core
83 | 0.1.0
84 |
85 | ```
86 |
87 | ## How to use
88 |
89 | ```java
90 | Colander.toss("/some/input.ics")
91 | .removeDuplicateEvents()
92 | .removeEmptyEvents()
93 | .removePropertyContains(Property.SUMMARY, "Remove me")
94 | .removeDescriptionContains("Remove me 2")
95 | // Generic replace in property
96 | .replaceInProperty(Property.DESCRIPTION, "L.ne", "Line")
97 | // Convenience: replace in property summary
98 | .replaceInSummary("Replace", "Replace!")
99 | .filter(event -> {
100 | System.out.println(event.toString());
101 | return Optional.of(event);
102 | })
103 | .rinse()
104 | .toFile("/some/output.ics");
105 | ```
106 |
107 | Under the hood, colander uses [ical4j](https://github.com/ical4j/ical4j). You can get an instance of the result like so
108 |
109 | ```java
110 | Calendar cal = Colander.toss("/some/input.ics")
111 | // ...
112 | .rinse()
113 | .toCalendar("/some/output.ics");
114 | ```
115 |
116 | More examples can be found in the
117 | * CLI module (see [ColanderCli](cli/src/main/java/info/schnatterer/colander/cli/ColanderCli.java)) and
118 | * integration test for core (see [ColanderITCase](core/src/test/java/info/schnatterer/colander/ColanderITCase.java))
119 |
120 |
--------------------------------------------------------------------------------
/cli/src/main/java/info/schnatterer/colander/cli/Arguments.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander.cli;
25 |
26 | import com.beust.jcommander.DynamicParameter;
27 | import com.beust.jcommander.Parameter;
28 |
29 | import java.util.ArrayList;
30 | import java.util.HashMap;
31 | import java.util.List;
32 | import java.util.Map;
33 |
34 | /**
35 | * Value object that holds the arguments passed to CLI.
36 | */
37 | // JCommander involves a lot of annotation magic, which leads to a lot of warnings which don't really apply here
38 | @SuppressWarnings({"unused", "MismatchedQueryAndUpdateOfCollection", "FieldCanBeLocal"})
39 | public class Arguments {
40 |
41 | /** List of unnamed arguments.*/
42 | @Parameter(required = true, description = " [")
43 | private List mainArguments = new ArrayList<>();
44 |
45 | @DynamicParameter(names = "--replace-summary", description = "Replace in summary calender components (regex)")
46 | private Map replaceInSummary = new HashMap<>();
47 |
48 | @DynamicParameter(names = "--replace-description", description = "Replace in description of calender components (regex)")
49 | private Map replaceInDescription = new HashMap<>();
50 |
51 | @Parameter(names = "--remove-summary", description = "Remove calender component when summary contains expression")
52 | private List removeSummaryContains = new ArrayList<>();
53 |
54 | @Parameter(names = "--remove-description", description = "Remove calender component when description contains expression")
55 | private List removeDescriptionContains = new ArrayList<>();
56 |
57 | @Parameter(names = "--remove-duplicate-events", description = "Remove event when summary, description, start date or end date are the same in another event")
58 | private boolean removeDuplicateEvents = false;
59 |
60 | @Parameter(names = "--remove-empty-events", description = "Remove events when summary and description are empty")
61 | private boolean removeEmptyEvents = false;
62 |
63 | @Parameter(names = "--help", help = true, description = "(optional) Show this message")
64 | private boolean help;
65 |
66 | /**
67 | * @return input file name. Never {@code null}.
68 | */
69 | public String getInputFile() {
70 | return mainArguments.get(0);
71 | }
72 |
73 | /**
74 | * @return output file name. Can be {@code null}!
75 | */
76 | public String getOutputFile() {
77 | String outputFile = null;
78 | if (mainArguments.size() > 1) {
79 | outputFile = mainArguments.get(1);
80 | }
81 | return outputFile;
82 | }
83 |
84 | /**
85 | * @return pairs of regexes to be replaced by each other within the summary. Replace key by value. Never {@code null}.
86 | */
87 | public Map getReplaceInSummary() { return replaceInSummary; }
88 |
89 | /**
90 | * @return pairs of regexes to be replaced by each other within the description. Replace key by value. Never {@code null}.
91 | */
92 | public Map getReplaceInDescription() { return replaceInDescription; }
93 |
94 | /**
95 | * @return the terms that when contained in summary, lead to removal.
96 | */
97 | public List getRemoveSummaryContains() { return removeSummaryContains; }
98 |
99 | /**
100 | * @return the terms that when contained in description, lead to removal.
101 | */
102 | public List getRemoveDescriptionContains() {
103 | return removeDescriptionContains;
104 | }
105 |
106 | /**
107 | * @return {@code true} when duplicates should be removed. Otherwise {@code false}.
108 | */
109 | public boolean isRemoveDuplicateEvents() { return removeDuplicateEvents; }
110 |
111 | /**
112 | * @return {@code true} when empty events should be removed. Otherwise {@code false}.
113 | */
114 | public boolean isRemoveEmptyEvents() { return removeEmptyEvents; }
115 |
116 | /**
117 | * @return {@code true} when help argument was passed. Otherwise {@code false}.
118 | */
119 | public boolean isHelp() { return help; }
120 |
121 | @Override
122 | public String toString() {
123 | return "Arguments{" +
124 | "mainArguments=" + mainArguments +
125 | ", replaceInSummary=" + replaceInSummary +
126 | ", replaceInDescription=" + replaceInDescription +
127 | ", removeSummaryContains=" + removeSummaryContains +
128 | ", removeDuplicateEvents=" + removeDuplicateEvents +
129 | ", removeEmptyEvents=" + removeEmptyEvents +
130 | ", help=" + help +
131 | '}';
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/test-lib/src/main/java/info/schnatterer/colander/test/ITCases.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander.test;
25 |
26 | import info.schnatterer.colander.Properties;
27 | import net.fortuna.ical4j.data.CalendarBuilder;
28 | import net.fortuna.ical4j.data.ParserException;
29 | import net.fortuna.ical4j.model.Calendar;
30 | import net.fortuna.ical4j.model.ComponentList;
31 | import net.fortuna.ical4j.model.Property;
32 | import net.fortuna.ical4j.model.component.CalendarComponent;
33 | import org.junit.rules.TemporaryFolder;
34 |
35 | import java.io.*;
36 | import java.nio.file.Files;
37 | import java.util.Arrays;
38 | import java.util.HashSet;
39 | import java.util.List;
40 | import java.util.Set;
41 | import java.util.stream.Collectors;
42 |
43 | import static org.junit.Assert.assertEquals;
44 | import static org.junit.Assert.assertTrue;
45 |
46 | /**
47 | * Constants used for testing in all modules.
48 | */
49 | public class ITCases {
50 | private static final String ICS_FILE = "ColanderIT.ics";
51 |
52 | private ITCases() {
53 | }
54 |
55 | /**
56 | * @return the absolute file path to test ICS file
57 | */
58 | public static String getFilePathTestIcs(TemporaryFolder folder) throws IOException {
59 | return getFilePathTestIcs(ICS_FILE, folder);
60 | }
61 |
62 | /**
63 | * Verifies that a parsed ICS is as expected.
64 | */
65 | @SuppressWarnings("squid:S1160") // This is a test-lib. Don't try to win a trophy for its design.
66 | public static void verifyParsedIcs(String inputPath, String outputPath) throws IOException, ParserException {
67 | Calendar originalCal = new CalendarBuilder().build(new FileInputStream(inputPath));
68 | Calendar filteredCal = new CalendarBuilder().build(new FileInputStream(outputPath));
69 | List filteredComponents = filteredCal.getComponents();
70 | ComponentList originalComponents = originalCal.getComponents();
71 | assertEquals("Number of components", originalComponents.size() - 6L, filteredComponents.size());
72 | CalendarComponent duplicate = findComponentBySummary(filteredComponents, "Duplicate");
73 | CalendarComponent replacedEvent = findComponentBySummary(filteredComponents, "event Replace");
74 | assertEquals("Replaced event description", "FirstLine\nSecondLine\nThirdLine\n",
75 | replacedEvent.getProperty(Property.DESCRIPTION).getValue());
76 | assertEquals("Replaced event summary", "event Replace!",
77 | replacedEvent.getProperty(Property.SUMMARY).getValue());
78 |
79 | CalendarComponent replacedToDo = findComponentBySummary(filteredComponents, "TDO Replace");
80 | assertEquals("Replaced todo summary", "TDO Replace!",
81 | replacedToDo.getProperty(Property.SUMMARY).getValue());
82 |
83 | // Check unfiltered components
84 | Set changedComponents = new HashSet<>(Arrays.asList(duplicate, replacedEvent, replacedToDo));
85 | filteredComponents.stream()
86 | .filter(component -> !changedComponents.contains(component))
87 | .forEach(unchangedComponent ->
88 | assertTrue("Unfiltered calender component not found in inut calender. Was it changed? Component: "
89 | + unchangedComponent, originalComponents.contains(unchangedComponent)));
90 | }
91 |
92 | /**
93 | * Visible for testing
94 | */
95 | static String getFilePathTestIcs(String path, TemporaryFolder folder) throws IOException {
96 | InputStream testIcsFileStream = ITCases.class.getClassLoader().getResourceAsStream(path);
97 | if (testIcsFileStream == null) {
98 | throw new AssertionError("Test ICS file not found");
99 | }
100 |
101 | // Write ics file to temporary folder, to also work when this module is a jar dependecy
102 | File newIcsFile = folder.newFile();
103 | Files.write(newIcsFile.toPath(), read(testIcsFileStream).getBytes("UTF-8"));
104 | return newIcsFile.getAbsolutePath();
105 | }
106 |
107 | private static CalendarComponent findComponentBySummary(List events, String summaryContains) {
108 | List filteredEvents = events.stream()
109 | .filter(event -> Properties.getSummaryValue(event).map(value -> value.contains(summaryContains)).orElse(false))
110 | .collect(Collectors.toList());
111 | assertEquals("Expected number of events with summary: " + summaryContains, 1, filteredEvents.size());
112 | return filteredEvents.get(0);
113 | }
114 |
115 | private static String read(InputStream input) throws IOException {
116 | try (BufferedReader buffer = new BufferedReader(new InputStreamReader(input))) {
117 | return buffer.lines().collect(Collectors.joining("\n"));
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | #!groovy
2 | /**
3 | * The MIT License (MIT)
4 | *
5 | * Copyright (c) 2017 Johannes Schnatterer
6 | *
7 | * Permission is hereby granted, free of charge, to any person obtaining a copy
8 | * of this software and associated documentation files (the "Software"), to deal
9 | * in the Software without restriction, including without limitation the rights
10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | * copies of the Software, and to permit persons to whom the Software is
12 | * furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in all
15 | * copies or substantial portions of the Software.
16 | *
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | * SOFTWARE.
24 | */
25 |
26 | node { // No specific label
27 |
28 | properties([
29 | // Keep only the last 10 build to preserve space
30 | //buildDiscarder(logRotator(numToKeepStr: '10')),
31 | [$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', numToKeepStr: '10']],
32 | // Configure GitHub project in order to start builds on push
33 | [$class: 'GithubProjectProperty', projectUrlStr: 'https://github.com/schnatterer/colander'],
34 | pipelineTriggers([[$class: 'GitHubPushTrigger']]),
35 | // Don't run concurrent builds for a branch, because they use the same workspace directory
36 | disableConcurrentBuilds()
37 | ])
38 |
39 | def CREDENTIALS = [
40 | $class : 'StringBinding',
41 | credentialsId: 'sonarqube',
42 | variable : 'authToken'
43 | ]
44 |
45 | //def sonarQubeVersion = 'sonar5-6'
46 | // TODO once withSonarQubeEnv closure works, use sonarQubeVersion and remove other SQ properties bellow
47 | String SONAR_MAVEN_PLUGIN_VERSION = '3.2'
48 | String SONAR_HOST_URL = env.SONAR_HOST
49 |
50 | String emailRecipients = env.EMAIL_RECIPIENTS
51 |
52 | catchError {
53 |
54 | def mvnHome = tool 'M3.3'
55 | def javaHome = tool 'JDK8'
56 |
57 | Maven mvn = new Maven(this, mvnHome, javaHome)
58 | if ("master".equals(env.BRANCH_NAME)) {
59 | mvn.additionalArgs = "-DperformRelease"
60 | currentBuild.description = mvn.getVersion()
61 | }
62 |
63 | stage('Checkout') {
64 | checkout scm
65 | gitClean()
66 | }
67 |
68 | stage('Build') {
69 | // Run the maven build
70 | mvn 'clean install -DskipTests'
71 | archive '**/target/*.jar,**/target/*.zip'
72 | }
73 |
74 | //parallel unitTests: {
75 | stage('Unit Test') {
76 | mvn 'test'
77 | }
78 | //}, integrationTests: {
79 | stage('Integration Test') {
80 | mvn 'verify -DskipUnitTests'
81 | }
82 | //}, failFast: true
83 |
84 | stage('SonarQube') {
85 | //withSonarQubeEnv(sonarQubeVersion) {
86 | // Results in this error https://issues.jenkins-ci.org/browse/JENKINS-39346
87 | // mvn "$SONAR_MAVEN_GOAL -Dsonar.host.url=$SONAR_HOST_URL",
88 | // // exclude generated code in target folder
89 | // "-Dsonar.exclusions=target/**"
90 | //}
91 |
92 | withCredentials([CREDENTIALS]) {
93 | //noinspection GroovyAssignabilityCheck
94 | mvn "org.codehaus.mojo:sonar-maven-plugin:${SONAR_MAVEN_PLUGIN_VERSION}:sonar -Dsonar.host.url=${SONAR_HOST_URL} " +
95 | "-Dsonar.login=$authToken " +
96 | // Exclude generated code in target folder
97 | "-Dsonar.exclusions=target/** "
98 | //+ sonarBranchProperty
99 | }
100 | }
101 | }
102 |
103 | // Archive Unit and integration test results, if any
104 | junit allowEmptyResults: true, testResults: '**/target/failsafe-reports/TEST-*.xml,**/target/surefire-reports/TEST-*.xml'
105 |
106 | // email on fail
107 | step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: emailRecipients, sendToIndividuals: true])
108 | }
109 |
110 | class Maven implements Serializable {
111 | def mvnHome
112 | def javaHome
113 | def script
114 |
115 | // Args added to each mvn call
116 | String additionalArgs = ""
117 |
118 | Maven(script, mvnHome, javaHome) {
119 | this.script = script
120 | this.mvnHome = mvnHome
121 | this.javaHome = javaHome
122 | }
123 |
124 | def call(args) {
125 | mvn(args)
126 | }
127 |
128 | def mvn(String args) {
129 | // Apache Maven related side notes:
130 | // --batch-mode : recommended in CI to inform maven to not run in interactive mode (less logs)
131 | // -V : strongly recommended in CI, will display the JDK and Maven versions in use.
132 | // Very useful to be quickly sure the selected versions were the ones you think.
133 | // -U : force maven to update snapshots each time (default : once an hour, makes no sense in CI).
134 | // -Dsurefire.useFile=false : useful in CI. Displays test errors in the logs directly (instead of
135 | // having to crawl the workspace files to see the cause).
136 |
137 | // Advice: don't define M2_HOME in general. Maven will autodetect its root fine.
138 | script.withEnv(["JAVA_HOME=${javaHome}", "PATH+MAVEN=${mvnHome}/bin:${script.env.JAVA_HOME}/bin"]) {
139 | script.sh "${mvnHome}/bin/mvn --batch-mode -V -U -e -Dsurefire.useFile=false --settings ${script.env.HOME}/.m2/settings.xml ${args + " " + additionalArgs}"
140 | }
141 | }
142 |
143 | String getVersion() {
144 | def matcher = script.readFile('pom.xml') =~ '(.+)'
145 | matcher ? matcher[0][1] : null
146 | }
147 | }
148 |
149 | void gitClean() {
150 | // Remove all untracked files
151 | sh "git clean -df"
152 | //Clear all unstaged changes
153 | sh 'git checkout -- .'
154 | }
155 |
--------------------------------------------------------------------------------
/core/src/test/java/info/schnatterer/colander/FilterChainTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.model.Calendar;
27 | import net.fortuna.ical4j.model.ComponentList;
28 | import net.fortuna.ical4j.model.Date;
29 | import net.fortuna.ical4j.model.component.*;
30 | import net.fortuna.ical4j.model.property.Summary;
31 | import org.junit.Before;
32 | import org.junit.Test;
33 | import org.mockito.invocation.InvocationOnMock;
34 | import org.mockito.stubbing.Answer;
35 |
36 | import java.util.Arrays;
37 | import java.util.List;
38 | import java.util.Optional;
39 |
40 | import static org.assertj.core.api.Assertions.assertThat;
41 | import static org.junit.Assert.assertFalse;
42 | import static org.junit.Assert.assertTrue;
43 | import static org.mockito.ArgumentMatchers.any;
44 | import static org.mockito.Mockito.*;
45 |
46 | public class FilterChainTest {
47 |
48 | private ColanderFilter passThroughFilter1 = mock(ColanderFilter.class);
49 | private ColanderFilter passThroughFilter2 = mock(ColanderFilter.class);
50 | private VEvent inputEvent = new VEvent();
51 |
52 | @Before
53 | public void setUp() {
54 | when(passThroughFilter1.apply(any(VEvent.class))).thenAnswer(new PassThroughAnswer());
55 | when(passThroughFilter2.apply(any(VEvent.class))).thenAnswer(new PassThroughAnswer());
56 | inputEvent.getProperties().add(new Summary(""));
57 | }
58 |
59 | @Test
60 | public void testParse() {
61 | FilterChain pipe = new FilterChain(Arrays.asList(passThroughFilter1, passThroughFilter2));
62 |
63 | VEvent event1 = new VEvent(new Date(), "event1");
64 | VEvent event2 = new VEvent(new Date(), "event2");
65 | List otherComponents = Arrays.asList(new VToDo(), new VTimeZone(), new VAlarm(), new VFreeBusy(),
66 | new VAvailability(), new VVenue(), new VJournal(), new XComponent("xcomp"));
67 | Calendar inputCalender = new Calendar(new ComponentList() {{
68 | add(event1);
69 | add(event2);
70 | addAll(otherComponents);
71 | }});
72 |
73 | Calendar outputCalendar = pipe.run(inputCalender);
74 | verify(passThroughFilter1).apply(event1);
75 | verify(passThroughFilter1).apply(event2);
76 | verify(passThroughFilter2).apply(event1);
77 | verify(passThroughFilter2).apply(event2);
78 | assertTrue("Event 1 not in output calender", outputCalendar.getComponents().contains(event1));
79 | assertTrue("Event 2 not in output calender", outputCalendar.getComponents().contains(event2));
80 | otherComponents.forEach( calendarComponent -> outputCalendar.getComponents().contains(calendarComponent));
81 | }
82 |
83 | @Test
84 | public void testParseDelete() {
85 |
86 | VEvent event1 = new VEvent(new Date(), "event1");
87 | VEvent event2 = new VEvent(new Date(), "event2");
88 | Calendar inputCalender = new Calendar(new ComponentList() {{
89 | add(event1);
90 | add(event2);
91 | }});
92 |
93 | ColanderFilter deleteEventFilter = mock(ColanderFilter.class);
94 | when(deleteEventFilter.apply(any(VEvent.class))).thenAnswer(new PassThroughAnswer());
95 | when(deleteEventFilter.apply(event1)).thenReturn(Optional.empty());
96 | FilterChain pipe = new FilterChain(Arrays.asList(passThroughFilter1, deleteEventFilter, passThroughFilter2));
97 |
98 | Calendar outputCalendar = pipe.run(inputCalender);
99 | verify(passThroughFilter1).apply(event1);
100 | verify(passThroughFilter1).apply(event2);
101 | verify(deleteEventFilter).apply(event1);
102 | verify(deleteEventFilter).apply(event2);
103 | verify(passThroughFilter2, never()).apply(event1);
104 | verify(passThroughFilter2).apply(event2);
105 | assertFalse("Event 1 in output calender", outputCalendar.getComponents().contains(event1));
106 | assertTrue("Event 2 not in output calender", outputCalendar.getComponents().contains(event2));
107 | }
108 |
109 | @Test
110 | public void testFilterEvent() {
111 | FilterChain pipe = new FilterChain(Arrays.asList(passThroughFilter1, passThroughFilter2));
112 |
113 | Optional actualFilteredEvent = pipe.filterEvent(inputEvent);
114 | verify(passThroughFilter1).apply(inputEvent);
115 | verify(passThroughFilter2).apply(inputEvent);
116 | assertThat(actualFilteredEvent).hasValue(inputEvent);
117 | }
118 |
119 | @Test
120 | public void testFilterEventDelete() {
121 | ColanderFilter filter2 = mock(ColanderFilter.class);
122 | FilterChain pipe = new FilterChain(Arrays.asList(passThroughFilter1, filter2, passThroughFilter2));
123 |
124 | Optional vEvent = pipe.filterEvent(inputEvent);
125 | verify(passThroughFilter1).apply(inputEvent);
126 | verify(filter2).apply(inputEvent);
127 | verify(passThroughFilter2, never()).apply(inputEvent);
128 | assertThat(vEvent).withFailMessage("Event not deleted").isEmpty();
129 | }
130 |
131 | private static class PassThroughAnswer implements Answer> {
132 | @Override
133 | public Optional answer(InvocationOnMock invocationOnMock) throws Throwable {
134 | return Optional.of((VEvent) invocationOnMock.getArguments()[0]);
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/core/src/main/java/info/schnatterer/colander/ColanderIO.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.data.CalendarBuilder;
27 | import net.fortuna.ical4j.data.CalendarOutputter;
28 | import net.fortuna.ical4j.data.ParserException;
29 | import net.fortuna.ical4j.model.Calendar;
30 | import net.fortuna.ical4j.validate.ValidationException;
31 | import org.slf4j.Logger;
32 | import org.slf4j.LoggerFactory;
33 |
34 | import java.io.*;
35 | import java.nio.file.FileAlreadyExistsException;
36 | import java.time.LocalDateTime;
37 | import java.time.format.DateTimeFormatter;
38 |
39 | /**
40 | * Handles in and output of calenders conveniently.
41 | */
42 | class ColanderIO {
43 | private static final Logger LOG = LoggerFactory.getLogger(ColanderIO.class);
44 | static final String DATE_TIME_FORMAT_FILE_NAME = "yyyyMMddHHmmss";
45 |
46 | /**
47 | * Creates calendar object from an ical file from a create a calender object
48 | *
49 | * @param filePath the path to the ical file
50 | * @return an object representing the ical file
51 | * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for
52 | * some other reason cannot be opened forreading.
53 | * @throws IOException where an error occurs reading data from the specified stream
54 | * @throws ColanderParserException where an error occurs parsing data from the stream
55 | */
56 | Calendar read(String filePath) throws IOException {
57 | return read(new FileInputStream(filePath));
58 | }
59 |
60 | /**
61 | * Creates calendar object from an ical stream from a create a calender object
62 | *
63 | * @param input a stream containg the ical file
64 | * @return an object representing the ical file
65 | * @throws FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for
66 | * some other reason cannot be opened forreading.
67 | * @throws IOException where an error occurs reading data from the specified stream
68 | * @throws ColanderParserException where an error occurs parsing data from the stream
69 | */
70 | Calendar read(InputStream input) throws IOException {
71 | LOG.info("Reading calendar file...");
72 |
73 | try {
74 | return createCalenderBuilder().build(input);
75 | } catch (ParserException e) {
76 | throw new ColanderParserException(e);
77 | }
78 | }
79 |
80 | /**
81 | * Writes a calender object to a file.
82 | *
83 | * @param cal the iCal to write
84 | * @param outputPath the file to write the modified iCal file to. When {@code null}, a new filename is generated
85 | * from {@code inputFilePath}.
86 | * @param inputFilePath input file path. Only needed when output path is {@code null}.
87 | * @throws FileNotFoundException if the file exists but is a directory
88 | * rather than a regular file, does not exist but cannot
89 | * be created, or cannot be opened for any other reason
90 | * @throws IOException thrown when unable to write to output stream
91 | * @throws ColanderParserException where calendar validation fails
92 | * @throws FileAlreadyExistsException if the file exists. Colander is not going to overwrite any files.
93 | */
94 | void write(Calendar cal, String outputPath, String inputFilePath) throws IOException {
95 | String actualPath = outputPath;
96 | if (actualPath == null) {
97 | actualPath = generateOutputPath(inputFilePath);
98 | }
99 | if (new File(actualPath).exists()) {
100 | throw new FileAlreadyExistsException(actualPath, null, "File already exists. Not going to overwrite it.");
101 | }
102 | LOG.info("Writing output to {}", actualPath);
103 | try (OutputStream outputStream = createOutputStream(actualPath)) {
104 | CalendarOutputter calendarOutputter = createCalendarOutputter();
105 | try {
106 | calendarOutputter.output(cal, outputStream);
107 | } catch (ValidationException e) {
108 | throw new ColanderParserException(e);
109 | }
110 | }
111 | }
112 |
113 | private String generateOutputPath(String inputFilePath) {
114 | if (inputFilePath == null) {
115 | throw new ColanderParserException("Both input and output file paths are null. Can't write result.");
116 | }
117 |
118 | int extensionSeparator = inputFilePath.lastIndexOf('.');
119 | if (extensionSeparator < 0) {
120 | extensionSeparator = inputFilePath.length();
121 | }
122 | return inputFilePath.substring(0, extensionSeparator)
123 | + '-'
124 | + LocalDateTime.now().format(DateTimeFormatter.ofPattern(DATE_TIME_FORMAT_FILE_NAME))
125 | + inputFilePath.substring(extensionSeparator);
126 | }
127 |
128 | /**
129 | * Visible for testing
130 | */
131 | CalendarBuilder createCalenderBuilder() { return new CalendarBuilder(); }
132 |
133 | /**
134 | * Visible for testing
135 | */
136 | CalendarOutputter createCalendarOutputter() {
137 | // Don't validate output. SISO.
138 | return new CalendarOutputter(false);
139 | }
140 |
141 | /**
142 | * Visible for testing
143 | */
144 | OutputStream createOutputStream(String outputFile) throws FileNotFoundException {
145 | return new FileOutputStream(outputFile);
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/cli/src/test/java/info/schnatterer/colander/cli/ArgumentsParserTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander.cli;
25 |
26 | import info.schnatterer.colander.cli.ArgumentsParser.ArgumentException;
27 | import org.hamcrest.junit.ExpectedException;
28 | import org.junit.Rule;
29 | import org.junit.Test;
30 | import uk.org.lidalia.slf4jtest.LoggingEvent;
31 | import uk.org.lidalia.slf4jtest.TestLogger;
32 | import uk.org.lidalia.slf4jtest.TestLoggerFactory;
33 | import uk.org.lidalia.slf4jtest.TestLoggerFactoryResetRule;
34 |
35 | import java.util.List;
36 | import java.util.Map;
37 |
38 | import static org.hamcrest.Matchers.*;
39 | import static org.junit.Assert.*;
40 |
41 | public class ArgumentsParserTest {
42 | private static final String PROGRAM_NAME = "progr";
43 |
44 | @Rule
45 | public ExpectedException expectedException = ExpectedException.none();
46 |
47 | /** Logger of class under test. */
48 | private static final TestLogger LOG = TestLoggerFactory.getTestLogger(ArgumentsParser.class);
49 |
50 | /** Rest logger before each test. **/
51 | @Rule
52 | public TestLoggerFactoryResetRule testLoggerFactoryResetRule = new TestLoggerFactoryResetRule();
53 |
54 | @Test
55 | public void read() throws Exception {
56 | Arguments args = read("input", "output");
57 |
58 | assertEquals("Input file", "input", args.getInputFile());
59 | assertEquals("Output file", "output", args.getOutputFile());
60 |
61 | assertFalse("Help", args.isHelp());
62 | assertFalse("Remove duplicates", args.isRemoveDuplicateEvents());
63 | assertFalse("Remove Empty", args.isRemoveEmptyEvents());
64 | assertTrue("Replace in summary", args.getReplaceInSummary().isEmpty());
65 | assertTrue("Remove summary contains", args.getRemoveSummaryContains().isEmpty());
66 | }
67 |
68 | @Test
69 | public void readInputOnly() throws Exception {
70 | Arguments args = read("input");
71 | assertEquals("Input file", "input", args.getInputFile());
72 | assertNull("Output file", args.getOutputFile());
73 | }
74 |
75 | @Test
76 | public void readNoMainArgs() throws Exception {
77 | expectedException.expect(ArgumentException.class);
78 | expectedException.expectMessage("Main parameters");
79 | read("");
80 | }
81 |
82 | @Test
83 | public void readReplaceInSummary() throws Exception {
84 | Map replaceInSummary =
85 | read("--replace-summary a=b", "--replace-summary", "\"\\r(?!\\n)=\\r\\n\"", "input", "output")
86 | .getReplaceInSummary();
87 | assertThat(replaceInSummary, hasEntry("a", "b"));
88 | assertThat(replaceInSummary, hasEntry("\\r(?!\\n)", "\\r\\n"));
89 | assertEquals("Unexpected amount of replace arguments", 2, replaceInSummary.size());
90 | }
91 |
92 | @Test
93 | public void readReplaceInDescription() throws Exception {
94 | Map replaceInDescription =
95 | read("--replace-description a=b", "--replace-description", "\"\\r(?!\\n)=\\r\\n\"", "input", "output")
96 | .getReplaceInDescription();
97 | assertThat(replaceInDescription, hasEntry("a", "b"));
98 | assertThat(replaceInDescription, hasEntry("\\r(?!\\n)", "\\r\\n"));
99 | assertEquals("Unexpected amount of replace arguments", 2, replaceInDescription.size());
100 | }
101 |
102 | @Test
103 | public void readRemoveSummaryContains() throws Exception {
104 | List removeSummaryContainsMultiple =
105 | read("--remove-summary", "a", "--remove-summary", "\"b c\"", "input", "output").getRemoveSummaryContains();
106 | List removeSummaryContainsCommaSyntax =
107 | read("--remove-summary", "\"a,b c\"", "input", "output").getRemoveSummaryContains();
108 | assertThat(removeSummaryContainsMultiple, contains("a", "b c"));
109 | assertEquals("Unexpected amount of replace arguments", 2, removeSummaryContainsMultiple.size());
110 | assertEquals("Multiple parameter syntax and comma syntax are not the same", removeSummaryContainsCommaSyntax, removeSummaryContainsMultiple);
111 | }
112 |
113 | @Test
114 | public void readRemoveDescriptionContains() throws Exception {
115 | List removeDescriptionContainsMultiple =
116 | read("--remove-description", "a", "--remove-description", "\"b c\"", "input", "output").getRemoveDescriptionContains();
117 | List removeDescriptionContainsCommaSyntax =
118 | read("--remove-description", "\"a,b c\"", "input", "output").getRemoveDescriptionContains();
119 | assertThat(removeDescriptionContainsMultiple, contains("a", "b c"));
120 | assertEquals("Unexpected amount of replace arguments", 2, removeDescriptionContainsMultiple.size());
121 | assertEquals("Multiple parameter syntax and comma syntax are not the same", removeDescriptionContainsCommaSyntax, removeDescriptionContainsMultiple);
122 | }
123 |
124 | @Test
125 | public void readRemoveDuplicates() {
126 | Arguments read = read("--remove-duplicate-events", "input", "output");
127 | assertTrue("Remove duplicates", read.isRemoveDuplicateEvents());
128 | }
129 |
130 | @Test
131 | public void readRemoveEmpty() {
132 | assertTrue("Remove empty", read("--remove-empty-events", "input", "output").isRemoveEmptyEvents());
133 | }
134 |
135 | @Test
136 | public void readHelp() throws Exception {
137 | assertTrue("Unexpected return on read()", read("input", "output", "--help").isHelp());
138 | assertThat("Unexpected log message", getLogEvent(0).getMessage(), containsString("Usage"));
139 | assertThat("Unexpected log message", getLogEvent(0).getMessage(), containsString(PROGRAM_NAME));
140 | }
141 |
142 | private Arguments read(String... argv) {
143 | return ArgumentsParser.read(argv, PROGRAM_NAME);
144 | }
145 |
146 | /**
147 | * @return the logging event at index. Fails if not enough logging events present.
148 | */
149 | private LoggingEvent getLogEvent(int index) {
150 | assertThat("Unexpected number of Log messages", LOG.getLoggingEvents().size(), greaterThan(index));
151 | return LOG.getLoggingEvents().get(index);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/core/src/test/java/info/schnatterer/colander/ColanderIOTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.data.CalendarBuilder;
27 | import net.fortuna.ical4j.data.CalendarOutputter;
28 | import net.fortuna.ical4j.data.ParserException;
29 | import net.fortuna.ical4j.model.Calendar;
30 | import net.fortuna.ical4j.validate.ValidationException;
31 | import org.hamcrest.junit.ExpectedException;
32 | import org.junit.Rule;
33 | import org.junit.Test;
34 | import org.junit.runner.RunWith;
35 | import org.mockito.Mock;
36 | import org.mockito.invocation.InvocationOnMock;
37 | import org.mockito.junit.MockitoJUnitRunner;
38 | import org.mockito.stubbing.Answer;
39 |
40 | import java.io.ByteArrayOutputStream;
41 | import java.io.FileNotFoundException;
42 | import java.io.InputStream;
43 | import java.io.OutputStream;
44 | import java.nio.file.FileAlreadyExistsException;
45 | import java.time.LocalDateTime;
46 | import java.time.format.DateTimeFormatter;
47 | import java.util.regex.Matcher;
48 | import java.util.regex.Pattern;
49 |
50 | import static org.hamcrest.Matchers.endsWith;
51 | import static org.hamcrest.Matchers.startsWith;
52 | import static org.junit.Assert.*;
53 | import static org.mockito.ArgumentMatchers.any;
54 | import static org.mockito.Mockito.*;
55 |
56 | @RunWith(MockitoJUnitRunner.class)
57 | public class ColanderIOTest {
58 | private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(ColanderIO.DATE_TIME_FORMAT_FILE_NAME);
59 |
60 | @Rule
61 | public ExpectedException expectedException = ExpectedException.none();
62 | @Mock
63 | private CalendarBuilder builder;
64 | @Mock
65 | private CalendarOutputter outputter;
66 | private OutputStream outStream = new ByteArrayOutputStream();
67 |
68 | private ColanderIO io = new ColanderIOForTest();
69 | private String outputPath;
70 |
71 | @Test
72 | public void read() throws Exception {
73 | Calendar expectedCalendar = mock(Calendar.class);
74 | InputStream stream = mock(InputStream.class);
75 | when(builder.build(stream)).thenReturn(expectedCalendar);
76 |
77 | Calendar actualCalender = io.read(stream);
78 | assertSame("Unexpected calendar returned", expectedCalendar, actualCalender);
79 | }
80 |
81 | @Test
82 | public void readException() throws Exception {
83 | ParserException expectedException = new ParserException("mocked exception message", 42);
84 | when(builder.build(any(InputStream.class))).thenThrow(expectedException);
85 |
86 | this.expectedException.expect(ColanderParserException.class);
87 | this.expectedException.expectMessage(expectedException.getMessage());
88 | io.read(mock(InputStream.class));
89 | }
90 |
91 | @Test
92 | public void write() throws Exception {
93 | String expectedFile = "expectedFile";
94 |
95 | io.write(mock(Calendar.class), expectedFile, null);
96 | //calendarOutputter.output is final and cant be mocked. So assert something else
97 | assertNotEquals("write() did not write anything", 0,
98 | ((ByteArrayOutputStream) outStream).toByteArray().length);
99 | }
100 |
101 | @Test
102 | public void writeValidationException() throws Exception {
103 | String expectedFile = "expectedFile";
104 | String expectedMessage = "mocked exception message";
105 | outStream = mock(OutputStream.class, new ThrowValidationExceptionOnEachMethodCall(expectedMessage));
106 | expectedException.expect(ColanderParserException.class);
107 | expectedException.expectMessage(expectedMessage);
108 |
109 | // Call method under test
110 | io.write(mock(Calendar.class), expectedFile, null);
111 | System.out.println(mockingDetails(outStream).getInvocations());
112 | verify(outStream, atLeastOnce()).close();
113 | }
114 |
115 | @Test
116 | public void writePathNull() throws Exception {
117 | LocalDateTime dateBefore = createComparableDateNow(LocalDateTime.now().format(formatter), formatter);
118 | io.write(mock(Calendar.class), null, "a/b.someEnding");
119 |
120 | assertThat(outputPath, startsWith("a/b"));
121 | assertThat(outputPath, endsWith(".someEnding"));
122 |
123 | verifyDateInNewFileName(outputPath, dateBefore, "\\.someEnding");
124 | }
125 |
126 | @Test
127 | public void writePathNullInputPathNoFileExtension() throws Exception {
128 | Calendar expectedCalendar = mock(Calendar.class);
129 | LocalDateTime dateBefore = createComparableDateNow(LocalDateTime.now().format(formatter), formatter);
130 |
131 | io.write(expectedCalendar, null, "a/b");
132 |
133 | assertThat(outputPath, startsWith("a/b"));
134 |
135 | verifyDateInNewFileName(outputPath, dateBefore, "");
136 | }
137 |
138 | @Test
139 | public void writeFileExists() throws Exception {
140 | expectedException.expect(FileAlreadyExistsException.class);
141 |
142 | io.write(mock(Calendar.class),
143 | // Just use this classes file to emulate an existing file
144 | createPathToClassFile(),
145 | null);
146 | }
147 |
148 | @Test
149 | public void writeFileAllArgumentsNull() throws Exception {
150 | expectedException.expect(ColanderParserException.class);
151 | expectedException.expectMessage("th input and output file paths are null");
152 | io.write(mock(Calendar.class), null, null);
153 | }
154 |
155 | /**
156 | * Answer that makes mock throw an {@link ValidationException} on each method call.
157 | */
158 | private static class ThrowValidationExceptionOnEachMethodCall implements Answer {
159 | String message;
160 |
161 | ThrowValidationExceptionOnEachMethodCall(String message) {
162 | this.message = message;
163 | }
164 |
165 | public Object answer(InvocationOnMock invocation) throws Throwable {
166 | throw new ValidationException(message);
167 | }
168 | }
169 |
170 | private String createPathToClassFile() {
171 | return getClass().getProtectionDomain().getCodeSource().getLocation().getPath()
172 | + getClass().getName().replace(".", "/") + ".class";
173 | }
174 |
175 | private LocalDateTime createComparableDateNow(String format, DateTimeFormatter formatter) {
176 | return LocalDateTime.parse(format, formatter);
177 | }
178 |
179 | private void verifyDateInNewFileName(String writtenPath, LocalDateTime dateBefore, String extension) {
180 | Pattern pattern = Pattern.compile("a/b-(.*)" + extension);
181 | Matcher matcher = pattern.matcher(writtenPath);
182 | assertTrue("Date not found in new file name", matcher.find());
183 | LocalDateTime newFileNameDate =
184 | LocalDateTime.parse(matcher.group(1), formatter);
185 | assertTrue("Date in new file name is unexpected. Expected equal or later than " + dateBefore + ", but was " + newFileNameDate,
186 | newFileNameDate.isAfter(dateBefore) || newFileNameDate.isEqual(dateBefore));
187 | }
188 |
189 | private class ColanderIOForTest extends ColanderIO {
190 | @Override
191 | CalendarBuilder createCalenderBuilder() { return builder; }
192 |
193 | @Override
194 | CalendarOutputter createCalendarOutputter() { return outputter; }
195 |
196 | @Override
197 | OutputStream createOutputStream(String outputFile) throws FileNotFoundException {
198 | ColanderIOTest.this.outputPath = outputFile;
199 | return outStream;
200 | }
201 | }
202 |
203 | }
204 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
26 |
28 | 4.0.0
29 |
30 | info.schnatterer.colander
31 | colander-parent
32 | 0.2.1-SNAPSHOT
33 |
34 | pom
35 |
36 | colander-parent
37 |
38 |
39 | scm:git:https://github.com/schnatterer/colander
40 | scm:git:https://github.com/schnatterer/colander
41 | https://github.com/schnatterer/colander.git
42 | HEAD
43 |
44 |
45 |
46 | UTF-8
47 | UTF-8
48 | ${project.basedir}
49 |
50 | ${skipTests}
51 |
52 | 1.7.22
53 | 1.2.0
54 | 0.8.3
55 |
56 |
57 |
58 | commons-lib
59 | test-lib
60 | core
61 | cli
62 |
63 |
64 |
65 |
66 | jitpack.io
67 | https://jitpack.io
68 |
69 |
70 |
71 |
72 |
73 |
74 | org.slf4j
75 | slf4j-api
76 | ${slf4j.version}
77 |
78 |
79 | ch.qos.logback
80 | logback-classic
81 | ${logback.version}
82 |
83 |
84 | junit
85 | junit
86 | 4.13.1
87 | test
88 |
89 |
90 | org.mockito
91 | mockito-core
92 | 2.27.0
93 | test
94 |
95 |
96 | org.hamcrest
97 | hamcrest-junit
98 | 2.0.0.0
99 | test
100 |
101 |
102 | org.assertj
103 | assertj-core
104 | 3.9.0
105 |
106 |
107 |
108 |
109 |
110 |
111 | junit
112 | junit
113 | test
114 |
115 |
116 | org.mockito
117 | mockito-core
118 | test
119 |
120 |
121 | org.hamcrest
122 | hamcrest-junit
123 | test
124 |
125 |
126 | org.assertj
127 | assertj-core
128 | test
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | org.apache.maven.plugins
137 | maven-assembly-plugin
138 | 3.0.0
139 |
140 |
141 | org.apache.maven.plugins
142 | maven-compiler-plugin
143 | 3.8.1
144 |
145 |
146 |
147 |
148 |
149 | org.apache.maven.plugins
150 | maven-compiler-plugin
151 |
152 | 11
153 |
154 |
155 |
156 |
157 | org.jacoco
158 | jacoco-maven-plugin
159 | ${jacoco.version}
160 |
161 |
162 | prepare-agent
163 | initialize
164 |
165 | prepare-agent
166 |
167 |
168 |
169 | prepare-agent-integration
170 | pre-integration-test
171 |
172 | prepare-agent-integration
173 |
174 |
175 |
176 |
177 |
178 |
179 | com.mycila
180 | license-maven-plugin
181 | 3.0
182 |
183 |
184 |
185 | JAVADOC_STYLE
186 |
187 |
188 | LICENSE
189 |
190 |
191 |
192 |
193 |
194 |
195 | check
196 |
197 |
198 |
199 |
200 |
201 | org.apache.maven.plugins
202 | maven-surefire-plugin
203 | 2.22.1
204 |
205 |
206 | ${skipUnitTests}
207 |
208 |
209 |
210 | org.apache.maven.plugins
211 | maven-failsafe-plugin
212 | 2.22.1
213 |
214 |
215 |
216 | integration-test
217 | verify
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
--------------------------------------------------------------------------------
/core/src/main/java/info/schnatterer/colander/Colander.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import net.fortuna.ical4j.model.Calendar;
27 | import net.fortuna.ical4j.model.Property;
28 |
29 | import java.io.IOException;
30 | import java.util.ArrayList;
31 | import java.util.List;
32 |
33 | /**
34 | * Public Interface of colander.
35 | */
36 | public class Colander {
37 | Colander() {}
38 |
39 | /**
40 | * Puts a calender into colander.
41 | *
42 | * @param filePath path to the ical file.
43 | * @return a new instance of the {@link ColanderBuilder}
44 | */
45 | public static ColanderBuilder toss(String filePath) {
46 | return new ColanderBuilder(filePath);
47 | }
48 |
49 | /**
50 | * Builder that allows configuring colander's filters fluently. Use {@link #rinse()} to apply.
51 | */
52 | public static class ColanderBuilder {
53 | List filters = new ArrayList<>();
54 | final String filePath;
55 |
56 | ColanderBuilder(String filePath) {
57 | this.filePath = filePath;
58 | }
59 |
60 | /**
61 | * Remove event when summary, description, start date or end date are the same in another event.
62 | *
63 | * @return a reference to this object.
64 | */
65 | public ColanderBuilder removeDuplicateEvents() {
66 | filters.add(new RemoveDuplicateEventFilter());
67 | return this;
68 | }
69 |
70 | /**
71 | * Removes event when it has
72 | *
73 | * - no summary and
74 | * - no description.
75 | *
76 | *
77 | * @return a reference to this object.
78 | */
79 | public ColanderBuilder removeEmptyEvents() {
80 | filters.add(new RemoveEmptyEventFilter());
81 | return this;
82 | }
83 |
84 | /**
85 | * Removes a calender component, when one of its properties contains a specific string.
86 | *
87 | * @param propertyName the event property to search
88 | * @param propertyContainsString remove component when it's property contains this string
89 | * @return a reference to this object.
90 | */
91 | public ColanderBuilder removePropertyContains(String propertyName, String propertyContainsString) {
92 | filters.add(new RemoveFilter(propertyContainsString, propertyName));
93 | return this;
94 | }
95 |
96 | /**
97 | * Removes a calender component, when its summary contains a specific string.
98 | *
99 | * @param summaryContainsString remove when summary contains this string
100 | * @return a reference to this object.
101 | */
102 | public ColanderBuilder removeSummaryContains(String summaryContainsString) {
103 | return removePropertyContains(Property.SUMMARY, summaryContainsString);
104 | }
105 |
106 | /**
107 | * Removes a calender component, when its summary contains a specific string.
108 | *
109 | * @param descriptionContainsString remove when summary contains this string
110 | * @return a reference to this object.
111 | */
112 | public ColanderBuilder removeDescriptionContains(String descriptionContainsString) {
113 | return removePropertyContains(Property.DESCRIPTION, descriptionContainsString);
114 | }
115 |
116 | /**
117 | * Replaces regex in a calender component's property (e.g. summary, description, ..)
118 | *
119 | * @param propertyName property to search
120 | * @param regex regex to match
121 | * @param stringToReplaceInSummary regex to replace matching regex
122 | * @return a reference to this object.
123 | */
124 | public ColanderBuilder replaceInProperty(String propertyName, String regex, String stringToReplaceInSummary) {
125 | filters.add(new ReplaceFilter(regex, stringToReplaceInSummary, propertyName));
126 | return this;
127 | }
128 |
129 | /**
130 | * Replaces regex in summary of a calender component.
131 | *
132 | * @param regex regex to match
133 | * @param stringToReplaceInSummary regex to replace matching regex
134 | * @return a reference to this object.
135 | */
136 | public ColanderBuilder replaceInSummary(String regex, String stringToReplaceInSummary) {
137 | return replaceInProperty(Property.SUMMARY, regex, stringToReplaceInSummary);
138 | }
139 |
140 | /**
141 | * Replaces regex in description of an event.
142 | *
143 | * @param regex regex to match
144 | * @param stringToReplaceInSummary regex to replace matching regex
145 | * @return a reference to this object.
146 | */
147 | public ColanderBuilder replaceInDescription(String regex, String stringToReplaceInSummary) {
148 | return replaceInProperty(Property.DESCRIPTION, regex, stringToReplaceInSummary);
149 | }
150 |
151 | /**
152 | * Adds a custom filter to colander.
153 | *
154 | * @param filter the event filter
155 | * @return a reference to this object.
156 | */
157 | public ColanderBuilder filter(ColanderFilter filter) {
158 | filters.add(filter);
159 | return this;
160 | }
161 |
162 | /**
163 | * Rinses colander's input, i.e. applies the filters to.
164 | * Terminates {@link ColanderBuilder} and returns a {@link ColanderResult} that allows further processing.
165 | *
166 | * @return a wrapper around the result that allows for further processing
167 | * @throws java.io.FileNotFoundException if the file does not exist, is a directory rather than a regular file, or for
168 | * some other reason cannot be opened forreading.
169 | * @throws IOException where an error occurs reading data from the specified stream
170 | * @throws ColanderParserException where an error occurs parsing data from the stream
171 | */
172 | public ColanderResult rinse() throws IOException {
173 | return new ColanderResult(filePath, createFilterChain().run(read(filePath)));
174 | }
175 |
176 | /**
177 | * Visible for testing.
178 | *
179 | * @return the calender at inputFilePath
180 | */
181 | Calendar read(String filePath) throws IOException {
182 | return new ColanderIO().read(filePath);
183 | }
184 |
185 | /**
186 | * Visible for testing.
187 | *
188 | * @return a filter chain containing the configured filters.
189 | */
190 | FilterChain createFilterChain() {
191 | return new FilterChain(filters);
192 | }
193 |
194 |
195 | }
196 |
197 | /**
198 | * Representation of rinsed calender, ready for further processing.
199 | */
200 | public static class ColanderResult {
201 | private final Calendar result;
202 | private final String inputFilePath;
203 |
204 | ColanderResult(String inputFilePath, Calendar result) {
205 | this.inputFilePath = inputFilePath;
206 | this.result = result;
207 | }
208 |
209 | /**
210 | * Write rinsed calender to ical file
211 | *
212 | * @param outputPath the path to the ical file. When {@code null}, a new filename is generated from
213 | * {@link #inputFilePath}.
214 | *
215 | * @throws java.io.FileNotFoundException if the file exists but is a directory
216 | * rather than a regular file, does not exist but cannot
217 | * be created, or cannot be opened for any other reason
218 | * @throws IOException thrown when unable to write to output stream
219 | * @throws ColanderParserException where calendar validation fails
220 | * @throws java.nio.file.FileAlreadyExistsException if the file exists. Colander is not going to overwrite any
221 | * files.
222 | */
223 | public void toFile(String outputPath) throws IOException {
224 | write(result, outputPath, inputFilePath);
225 | }
226 |
227 | /**
228 | * @return an in-memory-representation of rinsed calender.
229 | */
230 | public Calendar toCalendar() {
231 | return result;
232 | }
233 |
234 | /**
235 | * Visible for testing.
236 | */
237 | void write(Calendar result, String outputPath, String inputFilePath) throws IOException {
238 | new ColanderIO().write(result, outputPath, inputFilePath);
239 | }
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/core/src/test/java/info/schnatterer/colander/ColanderTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Johannes Schnatterer
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all
14 | * copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package info.schnatterer.colander;
25 |
26 | import info.schnatterer.colander.Colander.ColanderBuilder;
27 | import net.fortuna.ical4j.model.Calendar;
28 | import net.fortuna.ical4j.model.Property;
29 | import net.fortuna.ical4j.model.component.VEvent;
30 | import org.junit.Test;
31 |
32 | import java.io.IOException;
33 | import java.util.Iterator;
34 | import java.util.List;
35 | import java.util.Optional;
36 | import java.util.function.Consumer;
37 | import java.util.stream.Collectors;
38 |
39 | import static org.assertj.core.api.Assertions.assertThat;
40 | import static org.junit.Assert.*;
41 | import static org.mockito.ArgumentMatchers.any;
42 | import static org.mockito.Mockito.mock;
43 | import static org.mockito.Mockito.when;
44 |
45 | public class ColanderTest {
46 | private String expectedFilePath = "file";
47 | private FilterChain filterChain = mock(FilterChain.class);
48 | private Calendar cal = mock(Calendar.class);
49 |
50 | @Test
51 | public void toss() throws Exception {
52 | assertEquals(expectedFilePath, Colander.toss(expectedFilePath).filePath);
53 | }
54 |
55 | @Test
56 | public void removeDuplicates() throws Exception {
57 | ColanderBuilder colanderBuilder = Colander.toss(expectedFilePath).removeDuplicateEvents();
58 | assertThat(colanderBuilder.filters).first().isOfAnyClassIn(RemoveDuplicateEventFilter.class);
59 | }
60 |
61 | @Test
62 | public void removeEmptyEvents() throws Exception {
63 | ColanderBuilder colanderBuilder = Colander.toss(expectedFilePath).removeEmptyEvents();
64 | assertThat(colanderBuilder.filters).first().isOfAnyClassIn(RemoveEmptyEventFilter.class);
65 | }
66 |
67 | @Test
68 | public void replaceInProperty() throws Exception {
69 | String expectedProperty = "some property name";
70 | String expectedRegex = "a";
71 | String expectedReplacement = "b";
72 | replaceInProperty(expectedProperty, expectedRegex, expectedReplacement,
73 | colanderBuilder ->colanderBuilder.replaceInProperty(expectedProperty, expectedRegex, expectedReplacement));
74 | }
75 |
76 | @Test
77 | public void replaceInSummary() throws Exception {
78 | String expectedProperty = Property.SUMMARY;
79 | String expectedRegex = "a";
80 | String expectedReplacement = "b";
81 | replaceInProperty(expectedProperty, expectedRegex, expectedReplacement,
82 | colanderBuilder ->colanderBuilder.replaceInSummary(expectedRegex, expectedReplacement));
83 | }
84 |
85 | @Test
86 | public void replaceInDescription() throws Exception {
87 | String expectedProperty = Property.DESCRIPTION;
88 | String expectedRegex = "a";
89 | String expectedReplacement = "b";
90 | replaceInProperty(expectedProperty, expectedRegex, expectedReplacement,
91 | colanderBuilder ->colanderBuilder.replaceInDescription(expectedRegex, expectedReplacement));
92 | }
93 |
94 | @Test
95 | public void removePropertyContains() throws Exception {
96 | String expectedProperty = "some property name";
97 | String expectedString = "str";
98 | removePropertyContains(expectedProperty, expectedString,
99 | colanderBuilder -> colanderBuilder.removePropertyContains(expectedProperty, expectedString));
100 | }
101 |
102 | @Test
103 | public void removeSummaryContains() throws Exception {
104 | String expectedProperty = Property.SUMMARY;
105 | String expectedString = "str";
106 | removePropertyContains(expectedProperty, expectedString,
107 | colanderBuilder -> colanderBuilder.removeSummaryContains(expectedString));
108 | }
109 |
110 | @Test
111 | public void removeDescriptionContains() throws Exception {
112 | String expectedProperty = Property.DESCRIPTION;
113 | String expectedString = "str";
114 | removePropertyContains(expectedProperty, expectedString,
115 | colanderBuilder -> colanderBuilder.removeDescriptionContains(expectedString));
116 | }
117 |
118 | @Test
119 | public void filter() throws Exception {
120 | ColanderBuilder colanderBuilder = Colander.toss(expectedFilePath).filter(event -> Optional.empty());
121 | List allFilters = getFiltersByClass(colanderBuilder, ColanderFilter.class);
122 | assertEquals("Unexpected amount of filters found", 1, allFilters.size());
123 | allFilters.forEach(
124 | filter -> {
125 | VEvent event = mock(VEvent.class);
126 | assertThat(filter.apply(event)).isEmpty();
127 | }
128 | );
129 | }
130 |
131 | @Test
132 | public void maintainsFilterOrder() {
133 | ColanderBuilder colanderBuilder = Colander.toss(expectedFilePath)
134 | .removeSummaryContains("str")
135 | .replaceInSummary("a", "b")
136 | .removeEmptyEvents()
137 | .removeDuplicateEvents();
138 | Iterator filters = colanderBuilder.filters.iterator();
139 | assertTrue("Unexpected order", filters.next() instanceof RemoveFilter);
140 | assertTrue("Unexpected order", filters.next() instanceof ReplaceFilter);
141 | assertTrue("Unexpected order", filters.next() instanceof RemoveEmptyEventFilter);
142 | assertTrue("Unexpected order", filters.next() instanceof RemoveDuplicateEventFilter);
143 | }
144 |
145 |
146 | @Test
147 | public void rinseToCalendar() throws Exception {
148 | ColanderBuilder builder = new ColanderBuilderForTest(expectedFilePath);
149 |
150 | when(filterChain.run(any(Calendar.class))).thenReturn(cal);
151 |
152 | assertSame(cal, builder.rinse().toCalendar());
153 | }
154 |
155 | @Test
156 | public void rinseToFile() throws Exception {
157 | ColanderResultForTest colanderResult = new ColanderResultForTest("dontcare", cal);
158 | String expectedPath = "expectedPath";
159 |
160 | colanderResult.toFile(expectedPath);
161 |
162 | assertSame(expectedPath, colanderResult.writtenPath);
163 | assertSame(cal, colanderResult.writtenCal);
164 | }
165 |
166 | private List getFiltersByClass(ColanderBuilder colanderBuilder, Class clazz) {
167 | return colanderBuilder.filters.stream()
168 | .filter(clazz::isInstance)
169 | .map(clazz::cast)
170 | .collect(Collectors.toList());
171 | }
172 |
173 | private void replaceInProperty(String expectedProperty, String expectedRegex, String expectedReplacement, Consumer consumer) {
174 | ColanderBuilder colanderBuilder = Colander.toss(expectedFilePath);
175 | consumer.accept(colanderBuilder);
176 | List replaceFilters = getFiltersByClass(colanderBuilder, ReplaceFilter.class);
177 | assertEquals("Unexpected amount of filters found", 1, replaceFilters.size());
178 | replaceFilters.forEach(
179 | filter -> {
180 | assertEquals("Unexpected property", expectedProperty, filter.getPropertyName());
181 | assertEquals("Unexpected regex", expectedRegex, filter.getRegex());
182 | assertEquals("Unexpected stringToReplaceInSummary", expectedReplacement, filter.getStringToReplace());
183 | }
184 | );
185 | }
186 |
187 | private void removePropertyContains(String expectedProperty,String expectedString, Consumer consumer) {
188 | ColanderBuilder colanderBuilder = Colander.toss(expectedFilePath);
189 | consumer.accept(colanderBuilder);
190 | List removeFilters = getFiltersByClass(colanderBuilder, RemoveFilter.class);
191 | assertEquals("Unexpected amount of filters found", 1, removeFilters.size());
192 | removeFilters.forEach(
193 | filter -> {
194 | assertEquals("Unexpected property", expectedProperty, filter.getPropertyName());
195 | assertEquals("Unexpected summaryContainsString", expectedString, filter.getPropertyContainsString());
196 | }
197 | );
198 | }
199 |
200 | private class ColanderBuilderForTest extends ColanderBuilder {
201 | ColanderBuilderForTest(String filePath) {
202 | super(filePath);
203 | }
204 |
205 | @Override
206 | Calendar read(String filePath) throws IOException {
207 | return cal;
208 | }
209 |
210 | @Override
211 | FilterChain createFilterChain() {
212 | return filterChain;
213 | }
214 | }
215 |
216 | private class ColanderResultForTest extends Colander.ColanderResult {
217 | Calendar writtenCal;
218 | String writtenPath;
219 |
220 | ColanderResultForTest(String filePath, Calendar result) {
221 | super(filePath, result);
222 | }
223 |
224 | @Override
225 | void write(Calendar result, String path, String inputPath) throws IOException {
226 | writtenCal = result;
227 | writtenPath = path;
228 | }
229 | }
230 |
231 | }
232 |
--------------------------------------------------------------------------------
/cli/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
27 |
30 | 4.0.0
31 |
32 |
33 | info.schnatterer.colander
34 | colander-parent
35 | 0.2.1-SNAPSHOT
36 |
37 |
38 | colander-cli
39 | cli
40 |
41 | jar
42 |
43 |
44 | ${project.parent.basedir}
45 |
46 | ${project.version} (commit ${buildNumber}; ${maven.build.timestamp})
47 |
48 |
49 |
50 |
51 | ${project.parent.groupId}
52 | colander-core
53 | ${project.parent.version}
54 |
55 |
56 |
57 | ch.qos.logback
58 | logback-classic
59 | runtime
60 |
61 |
62 |
63 | com.beust
64 | jcommander
65 | 1.60
66 |
67 |
68 |
69 | com.cloudogu.versionName
70 | processor
71 | 2.1.0
72 |
73 | provided
74 |
75 |
76 |
77 |
78 | ${project.parent.groupId}
79 | colander-test-lib
80 | ${project.parent.version}
81 | test
82 |
83 |
84 |
85 | uk.org.lidalia
86 | slf4j-test
87 | 1.2.0
88 | test
89 |
90 |
91 |
92 |
93 | com.github.stefanbirkner
94 | system-rules
95 | 1.16.1
96 | test
97 |
98 |
99 |
100 |
101 | ${project.artifactId}-${project.version}
102 |
103 |
104 |
105 | org.apache.maven.plugins
106 | maven-jar-plugin
107 | 3.0.2
108 |
109 |
110 | ${project.artifactId}
111 |
112 |
113 | true
114 |
115 | lib/
116 | info.schnatterer.colander.cli.ColanderCli
117 |
118 |
119 | ${versionName}
120 |
121 |
122 |
123 |
124 |
125 |
126 | org.apache.maven.plugins
127 | maven-compiler-plugin
128 |
129 |
130 |
131 | -AversionName=${versionName}
132 |
133 |
134 |
135 |
136 |
137 |
138 | org.apache.maven.plugins
139 | maven-assembly-plugin
140 |
141 |
143 |
144 | package
145 |
146 | single
147 |
148 |
149 |
150 |
151 |
152 | org.apache.maven.plugins
153 | maven-surefire-plugin
154 |
155 |
156 |
157 | ch.qos.logback:logback-classic
158 |
159 |
160 |
161 |
162 |
163 | org.codehaus.mojo
164 | buildnumber-maven-plugin
165 | 1.4
166 |
167 |
168 |
169 | create
170 |
171 |
172 |
173 |
174 |
175 | true
176 |
177 | false
178 |
179 | false
180 | versiojn
181 | 7
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
192 | versionNameBuildNumber
193 |
194 |
195 | env.BUILD_NUMBER
196 |
197 |
198 |
199 |
200 | ${project.version} build #${env.BUILD_NUMBER} (${buildNumber}; ${maven.build.timestamp})
201 |
202 |
203 |
204 |
205 |
207 | versionNameForRelease
208 |
209 |
210 | performRelease
211 |
212 |
213 |
214 | ${project.version}
215 |
216 |
217 |
218 |
219 | assemble-zip
220 |
221 |
222 |
223 | !jar
224 |
225 |
226 |
227 |
228 |
229 | org.apache.maven.plugins
230 | maven-assembly-plugin
231 |
232 |
233 |
234 | src/main/assembly/assembly.xml
235 |
236 | false
237 |
238 |
239 |
240 |
241 |
242 |
243 | assemble-fat-jar
244 |
245 |
246 |
247 | jar
248 |
249 |
250 |
251 |
252 |
253 | org.apache.maven.plugins
254 | maven-assembly-plugin
255 |
256 |
257 |
258 | info.schnatterer.colander.cli.ColanderCli
259 |
260 |
261 | ${versionName}
262 |
263 |
264 |
265 | jar-with-dependencies
266 |
267 | false
268 |
269 |
270 |
271 |
272 | package
273 |
274 | single
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
--------------------------------------------------------------------------------