diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6b86bea1c..3a8029352 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,11 +14,9 @@ jobs: strategy: fail-fast: true matrix: - java: [11] + java: [17] os: [ubuntu-latest, macos-latest, windows-latest] include: - - os: ubuntu-latest - java: 17 - os: ubuntu-latest java: 21 @@ -32,7 +30,7 @@ jobs: shell: "bash" name: "Build on ${{ matrix.os }} with Java ${{ matrix.java }}" env: - DEFAULT_JAVA: 11 + DEFAULT_JAVA: 17 DEFAULT_OS: ubuntu-latest steps: @@ -45,7 +43,6 @@ jobs: with: distribution: 'temurin' java-version: | - 11 17 21 cache: 'maven' diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 600e3b28a..aa2db5938 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 11 + java-version: 17 cache: 'maven' - name: Initialize CodeQL diff --git a/.gitignore b/.gitignore index 6006b6f7e..c7879cc89 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ doc/**/*.html .idea/ *.iml pom.xml.versionsBackup +.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json index 878994ce6..d878e3547 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,6 +15,11 @@ "-Djava.util.logging.config.file=src/test/resources/logging.properties" ] }, + "[java]": { + "editor.detectIndentation": false, + "editor.tabSize": 4, + "editor.indentSize": "tabSize" + }, "sonarlint.connectedMode.project": { "connectionId": "itsallcode", "projectKey": "org.itsallcode.openfasttrace:openfasttrace-root" diff --git a/README.md b/README.md index b456a40ee..7f2d0f525 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ## What is OpenFastTrace? -OpenFastTrace (short OFT) is a requirement tracing suite. Requirement tracing helps you keeping track of whether you actually implemented everything you planned to in your specifications. It also identifies obsolete parts of your product and helps you getting rid of them. +OpenFastTrace (short OFT) is a requirement tracing suite. Requirement tracing keeps track of whether you actually implemented everything you planned to in your specifications. It also identifies obsolete parts of your product and helps you to get rid of them. You can learn more about requirement tracing and how to use OpenFastTrace in the [user guide](doc/user_guide.md). -Below you see a screenshot of of a HTML tracing report where OFT traces itself. You see a summary followed by a detail view of the traced requirements. +Below you see a screenshot of an HTML tracing report where OFT traces itself. You see a summary followed by a detail view of the traced requirements. OFT HTML tracing report @@ -31,7 +31,6 @@ Sonarcloud status: **User Guides** -* [📖 About us](doc/about_us.md) * [📖 User Guide](doc/user_guide.md) * [💲 Command Line Usage](core/src/main/resources/usage.txt) @@ -42,6 +41,7 @@ Sonarcloud status: * [📅 Upcoming Milestones](https://github.com/orgs/itsallcode/projects/3/views/3) * [🗨️ Discussion Board](https://github.com/itsallcode/openfasttrace/discussions) * [✨ OpenFastTrace Stories](https://github.com/itsallcode/openfasttrace/wiki/OFT-Stories) +* [ℹ️ About us](doc/about_us.md) **Information for Contributors** @@ -70,10 +70,10 @@ OpenFastTrace at it's core is a Java Archive (short "[JAR](https://docs.oracle.c ### Getting Pre-Built Packages -Pre-Built JAR files (called `openfasttrace-3.8.0.jar`) are available from the following places: +Pre-Built JAR files (called `openfasttrace-4.0.0.jar`) are available from the following places: -* [Maven Central](https://repo1.maven.org/maven2/org/itsallcode/openfasttrace/openfasttrace/3.8.0/openfasttrace-3.8.0.jar) -* [GitHub](https://github.com/itsallcode/openfasttrace/releases/download/3.8.0/openfasttrace-3.8.0.jar) +* [Maven Central](https://repo1.maven.org/maven2/org/itsallcode/openfasttrace/openfasttrace/4.0.0/openfasttrace-4.0.0.jar) +* [GitHub](https://github.com/itsallcode/openfasttrace/releases/download/4.0.0/openfasttrace-4.0.0.jar) Check our [developer guide](doc/developer_guide.md#getting-the-openfasttrace-library) to learn how to use the OFT JAR as dependency in your own code with popular build tools. @@ -81,7 +81,8 @@ Check our [developer guide](doc/developer_guide.md#getting-the-openfasttrace-lib ### Runtime Dependencies -OpenFastTrace 3.0.0 and above only needs a Java 11 (or later) runtime environment to run. Older versions of OpenFastTrace can run with Java 8. +OpenFastTrace 4.0.0 and above only needs a Java 17 (or later) runtime environment to run. OpenFastTrace until version 3.x.x supported Java 11. Versions prior to that ran with Java 8. +Note that only the latest version of OFT is actively supported. #### Installation of Runtime Dependencies on Linux @@ -89,14 +90,14 @@ OpenFastTrace 3.0.0 and above only needs a Java 11 (or later) runtime environmen If you just want to run OFT: - apt-get install openjdk-11-jre + apt-get install openjdk-17-jre ## Running OpenFastTrace The most basic variant to run OpenFastTrace is directly from the JAR file via the command line: ```bash -java -jar product/target/openfasttrace-3.8.0.jar trace /path/to/directory/being/traced +java -jar product/target/openfasttrace-4.0.0.jar trace /path/to/directory/being/traced ``` If you want to run OFT automatically as part of a continuous build, we recommend using our plugins for [Gradle](https://github.com/itsallcode/openfasttrace-gradle) and [Maven](https://github.com/itsallcode/openfasttrace-maven-plugin). diff --git a/api/.settings/org.eclipse.jdt.core.prefs b/api/.settings/org.eclipse.jdt.core.prefs index 6d1f31917..e2b1419eb 100644 --- a/api/.settings/org.eclipse.jdt.core.prefs +++ b/api/.settings/org.eclipse.jdt.core.prefs @@ -10,9 +10,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable.secondary= org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -129,7 +129,7 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index 1da8268b6..b16994285 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -12,7 +12,7 @@ exports org.itsallcode.openfasttrace.api.importer.tag.config; exports org.itsallcode.openfasttrace.api.exporter; exports org.itsallcode.openfasttrace.api.report; - // Required to run the EqualsVerifier: + // Workaround: Required for the EqualsVerifier opens org.itsallcode.openfasttrace.api.core; requires java.logging; diff --git a/api/src/main/java/org/itsallcode/openfasttrace/api/core/SpecificationItemId.java b/api/src/main/java/org/itsallcode/openfasttrace/api/core/SpecificationItemId.java index c804cdc0f..99dff42e7 100644 --- a/api/src/main/java/org/itsallcode/openfasttrace/api/core/SpecificationItemId.java +++ b/api/src/main/java/org/itsallcode/openfasttrace/api/core/SpecificationItemId.java @@ -36,7 +36,6 @@ public class SpecificationItemId implements Comparable + ITEM_NAME_PATTERN // + REVISION_SEPARATOR // + ITEM_REVISION_PATTERN; - // [impl->dsn~md.eb-markdown-id~1] private static final String LEGACY_ID = LEGACY_ID_NAME + ", *v" // + ITEM_REVISION_PATTERN; private static final int ARTIFACT_TYPE_MATCHING_GROUP = 1; @@ -370,8 +369,8 @@ private void parseId() } else { - throw new IllegalStateException( - "String '" + this.id + "' cannot be parsed to a specification item ID"); + throw new IllegalArgumentException("Invalid specification item ID format: \"" + this.id + + "\". Format must be ~~."); } } } diff --git a/api/src/test/java/org/itsallcode/openfasttrace/api/core/SpecificationItemIdTest.java b/api/src/test/java/org/itsallcode/openfasttrace/api/core/SpecificationItemIdTest.java deleted file mode 100644 index 056a6f3bc..000000000 --- a/api/src/test/java/org/itsallcode/openfasttrace/api/core/SpecificationItemIdTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.itsallcode.openfasttrace.api.core; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import nl.jqno.equalsverifier.EqualsVerifier; - -class SpecificationItemIdTest -{ - - @ParameterizedTest(name = "Parsing of id ''{0}'' succeeds") - @CsvSource(value = - { - "type~name~42, type, name, 42", - "type~name-with-dash~42, type, name-with-dash, 42", - "type~name.with.dot~42, type, name.with.dot, 42", - "type~name-trailing-~42, type, name-trailing-, 42", - }) - void parsingValidIdsSucceeds(final String id, final String expectedType, final String expectedName, - final int expectedRevision) - { - final SpecificationItemId parsedId = parseId(id); - assertAll( - () -> assertThat(parsedId.getArtifactType(), equalTo(expectedType)), - () -> assertThat(parsedId.getName(), equalTo(expectedName)), - () -> assertThat(parsedId.getRevision(), equalTo(expectedRevision))); - } - - @ParameterizedTest(name = "Parsing of id ''{0}'' fails") - @CsvSource(value = - { - "ty.pe~name~42", - "ty-pe~name~42", - "ty.pe~name~42", - "type~.name~42", - "type~name-with-trailing-dot.~42", - "type~name~rev", - "typeÄ~name~42", - "type~na.-me~42", - "~name~42", - "type~~42", - "type~name~" - }) - void parsingIllegalIdsFails(final String id) - { - final IllegalStateException exception = assertThrows(IllegalStateException.class, () -> parseId(id)); - assertThat(exception.getMessage(), - equalTo("String '" + id + "' cannot be parsed to a specification item ID")); - } - - @Test - void equalsContract() - { - EqualsVerifier.forClass(SpecificationItemId.class).verify(); - } - - @Test - void hasToString() - { - assertThat(parseId("type~name~42").toString(), equalTo("type~name~42")); - } - - @Test - void toRevisionWildcard() - { - final SpecificationItemId id = parseId("type~name~42").toRevisionWildcard(); - assertThat(id.getRevision(), equalTo(SpecificationItemId.REVISION_WILDCARD)); - assertThat(id.toString(), equalTo("type~name~" + SpecificationItemId.REVISION_WILDCARD)); - } - - private SpecificationItemId parseId(final String id) - { - return new SpecificationItemId.Builder(id).build(); - } -} diff --git a/api/src/test/java/org/itsallcode/openfasttrace/api/core/TestSpecificationItemId.java b/api/src/test/java/org/itsallcode/openfasttrace/api/core/TestSpecificationItemId.java index d328c4a86..c2a6dc95e 100644 --- a/api/src/test/java/org/itsallcode/openfasttrace/api/core/TestSpecificationItemId.java +++ b/api/src/test/java/org/itsallcode/openfasttrace/api/core/TestSpecificationItemId.java @@ -4,6 +4,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.itsallcode.openfasttrace.api.core.SpecificationItemId.createId; import static org.itsallcode.openfasttrace.api.core.SpecificationItemId.parseId; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertThrows; import org.itsallcode.openfasttrace.api.core.SpecificationItemId.Builder; @@ -19,60 +20,35 @@ // [utest->dsn~specification-item-id~1] class TestSpecificationItemId { - private static final int REVISION = 1; - private static final String NAME = "foo"; - private static final String ARTIFACT_TYPE_FEATURE = "feat"; - - @Test - void testCreateId() - { - final SpecificationItemId id = createId(ARTIFACT_TYPE_FEATURE, NAME, REVISION); - assertThat(id, equalTo(new Builder().artifactType(ARTIFACT_TYPE_FEATURE).name(NAME) - .revision(REVISION).build())); - } - - @Test - void testCreateIdWithoutRevision() - { - final SpecificationItemId id = createId(ARTIFACT_TYPE_FEATURE, NAME); - assertThat(id, equalTo(new Builder().artifactType(ARTIFACT_TYPE_FEATURE).name(NAME) - .revision(Integer.MIN_VALUE).build())); - } - - @Test - void testParseId_singleDigitRevision() - { - final SpecificationItemId id = parseId("feat~foo~1"); - assertThat(id.getArtifactType(), equalTo(ARTIFACT_TYPE_FEATURE)); - assertThat(id.getName(), equalTo(NAME)); - assertThat(id.getRevision(), equalTo(1)); - } - - @Test - void testParseId_multipleFragmentName() + @ParameterizedTest(name = "Parsing of ID ''{0}'' succeeds") + @CsvSource(value = { - final SpecificationItemId id = parseId("feat~foo.bar_zoo.baz-narf~1"); - assertThat(id.getArtifactType(), equalTo(ARTIFACT_TYPE_FEATURE)); - assertThat(id.getName(), equalTo("foo.bar_zoo.baz-narf")); - assertThat(id.getRevision(), equalTo(1)); - } - - @Test - void testParseId_umlautName() + "type~name~42, type, name, 42", + "type~name-with-dash~42, type, name-with-dash, 42", + "type~name.with.dot~42, type, name.with.dot, 42", + "type~name-trailing-~42, type, name-trailing-, 42", + "foobar~utf-8.compatible.äöü~333, foobar, utf-8.compatible.äöü, 333", + // Deprecated Elektrobit-style specification item ID + "'feat:foo, v1', feat, foo, 1" + }) + void parsingValidIdsSucceeds(final String id, final String expectedType, final String expectedName, + final int expectedRevision) { - final SpecificationItemId id = parseId("feat~änderung~1"); - assertThat(id.getArtifactType(), equalTo(ARTIFACT_TYPE_FEATURE)); - assertThat(id.getName(), equalTo("änderung")); - assertThat(id.getRevision(), equalTo(1)); + final SpecificationItemId parsedId = parseId(id); + assertAll( + () -> assertThat(parsedId.getArtifactType(), equalTo(expectedType)), + () -> assertThat(parsedId.getName(), equalTo(expectedName)), + () -> assertThat(parsedId.getRevision(), equalTo(expectedRevision))); } + // This is the wildcard revision test @Test - void testParseId_multipleDigitRevision() + void testCreateIdWithoutRevision() { - final SpecificationItemId id = parseId("feat~foo~999"); - assertThat(id.getArtifactType(), equalTo(ARTIFACT_TYPE_FEATURE)); - assertThat(id.getName(), equalTo(NAME)); - assertThat(id.getRevision(), equalTo(999)); + final SpecificationItemId id = createId("impl", "wildcard-feature"); + assertAll(() -> assertThat(id, equalTo(new Builder().artifactType("impl").name("wildcard-feature") + .revision(Integer.MIN_VALUE).build())), + () -> assertThat(SpecificationItemId.REVISION_WILDCARD, equalTo(Integer.MIN_VALUE))); } @Test @@ -84,18 +60,24 @@ void testParseId_IllegalNumberFormat() "Error parsing version number from specification item ID: \"feat~foo~999999999999999999999999999999999999999\"")); } - @ParameterizedTest - @CsvSource( - { "feat.foo~1", "foo~1", "req~foo", "req1~foo~1", "req.r~foo~1", "req~1foo~1", "req~.foo~1", "req~foo~-1", - // Wildcard revision: - "feat~foo~-2147483648" }) + @CsvSource({ + "feat.foo~1", + "foo~1", + "req~foo", + "req1~foo~1", + "req.r~foo~1", + "req~1foo~1", + "req~.foo~1", + "req~foo.~1", + "req~foo~-1", + }) + @ParameterizedTest(name = "Parsing of id ''{0}'' fails") void testParseId_mustFailForIllegalIds(final String illegalId) { - - final IllegalStateException exception = assertThrows(IllegalStateException.class, - () -> parseId(illegalId)); + final Throwable exception = assertThrows(IllegalArgumentException.class, () -> parseId(illegalId)); assertThat(exception.getMessage(), - equalTo("String '" + illegalId + "' cannot be parsed to a specification item ID")); + equalTo("Invalid specification item ID format: \"" + illegalId + "\". " + + "Format must be ~~.")); } @Test @@ -119,16 +101,7 @@ void testToStringRevisionWildcard() { final Builder builder = new Builder(); builder.artifactType("dsn").name("dummy").revisionWildcard(); - assertThat(builder.build().toString(), - equalTo("dsn~dummy~" + SpecificationItemId.REVISION_WILDCARD)); - } - - void testCreate_WithRevisionWildcard() - { - final SpecificationItemId id = createId(ARTIFACT_TYPE_FEATURE, NAME); - assertThat(id.getArtifactType(), equalTo(ARTIFACT_TYPE_FEATURE)); - assertThat(id.getName(), equalTo(NAME)); - assertThat(id.getRevision(), equalTo(SpecificationItemId.REVISION_WILDCARD)); + assertThat(builder.build().toString(), equalTo("dsn~dummy~" + SpecificationItemId.REVISION_WILDCARD)); } @Test diff --git a/core/.settings/org.eclipse.jdt.core.prefs b/core/.settings/org.eclipse.jdt.core.prefs index 2de9d8bc1..c5b728dbf 100644 --- a/core/.settings/org.eclipse.jdt.core.prefs +++ b/core/.settings/org.eclipse.jdt.core.prefs @@ -8,8 +8,8 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nullable.secondary= org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.APILeak=warning org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning @@ -119,7 +119,7 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/core/src/main/resources/openfasttrace_logo.svg b/core/src/main/resources/openfasttrace_logo.svg index 797443db3..824b26698 100644 --- a/core/src/main/resources/openfasttrace_logo.svg +++ b/core/src/main/resources/openfasttrace_logo.svg @@ -11,8 +11,8 @@ viewBox="2744 2980 11282 3960" version="1.1" id="svg20" - sodipodi:docname="openfasttrace_logo2.svg" - inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)" + sodipodi:docname="openfasttrace_logo.svg" + inkscape:version="1.2.2 (b0a8486541, 2022-12-01)" inkscape:export-filename="/home/sebastian/git/openfasttrace/src/main/resources/openfasttrace_logo.png" inkscape:export-xdpi="96" inkscape:export-ydpi="96" @@ -45,24 +45,26 @@ guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" - inkscape:window-width="3366" - inkscape:window-height="1376" + inkscape:window-width="3770" + inkscape:window-height="1523" id="namedview22" showgrid="false" - inkscape:zoom="0.42331562" - inkscape:cx="-317.72983" - inkscape:cy="159.45549" - inkscape:window-x="74" - inkscape:window-y="27" + inkscape:zoom="1.6932625" + inkscape:cx="200.20523" + inkscape:cy="178.64921" + inkscape:window-x="70" + inkscape:window-y="40" inkscape:window-maximized="1" inkscape:current-layer="svg20" - inkscape:pagecheckerboard="0" + inkscape:showpageshadow="2" + inkscape:pagecheckerboard="true" + inkscape:deskcolor="#d1d1d1" inkscape:document-units="in" /> + style="fill:#58c8fd;fill-opacity:1;stroke:#0a5ddd;stroke-width:111;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 4250.9919 3543.0069 L 3307.0002 5432.9993 L 4015.0051 6377.001 L 5668.9938 6377.001 L 6613.9928 4487.9934 L 6140.9897 3543.0069 L 4250.9919 3543.0069 z M 4723.9949 4487.9934 L 5668.9938 4487.9934 L 5195.9908 5432.9993 L 4250.9919 5432.9993 L 4723.9949 4487.9934 z " + transform="matrix(1.090922,0,0,1.2645031,-737.28966,-1311.9354)" /> dsn~tracing.outgoing-coverage-link-status~3] @Test void testDetectOutgoingLinkStatusCovered() { - final SpecificationItem covered = SpecificationItem.builder() // + final SpecificationItem covered = item() // .id(REQ, "covered", 1) // .addNeedsArtifactType(IMPL) // .build(); - final SpecificationItem covering = SpecificationItem.builder() // + final SpecificationItem covering = item() // .id(IMPL, "covering", 1) // .addCoveredId(REQ, "covered", 1) // .build(); @@ -69,7 +71,7 @@ private Optional findLinkedItem(final SpecificationItem @Test void testDetectOutgoingLinkStatusOrphaned() { - final SpecificationItem orphan = SpecificationItem.builder() // + final SpecificationItem orphan = item() // .id(IMPL, "orphan", 1) // .addCoveredId(createId(REQ, "non-existent", 1)) // .build(); @@ -81,11 +83,11 @@ void testDetectOutgoingLinkStatusOrphaned() @Test void testDetectOutgoingLinkStatusOutdated() { - final SpecificationItem covered = SpecificationItem.builder() // + final SpecificationItem covered = item() // .id(REQ, "this-is-newer-than-link", 2) // .addNeedsArtifactType(IMPL) // .build(); - final SpecificationItem covering = SpecificationItem.builder() // + final SpecificationItem covering = item() // .id(IMPL, "covering", 1) // .addCoveredId(REQ, "this-is-newer-than-link", 1) // .build(); @@ -100,11 +102,11 @@ void testDetectOutgoingLinkStatusOutdated() @Test void testDetectOutgoingLinkStatusPredated() { - final SpecificationItem covered = SpecificationItem.builder() // + final SpecificationItem covered = item() // .id(REQ, "this-is-older-than-link", 1) // .addNeedsArtifactType(IMPL) // .build(); - final SpecificationItem covering = SpecificationItem.builder() // + final SpecificationItem covering = item() // .id(IMPL, "covering", 1) // .addCoveredId(REQ, "this-is-older-than-link", 2) // .build(); @@ -119,15 +121,15 @@ void testDetectOutgoingLinkStatusPredated() @Test void testDetectOutgoingLinkStatusAmbiguous() { - final SpecificationItem covered = SpecificationItem.builder() // + final SpecificationItem covered = item() // .id(REQ, "covered", 1) // .addNeedsArtifactType(IMPL) // .build(); - final SpecificationItem duplicate = SpecificationItem.builder() // + final SpecificationItem duplicate = item() // .id(REQ, "covered", 1) // .addNeedsArtifactType(IMPL) // .build(); - final SpecificationItem covering = SpecificationItem.builder() // + final SpecificationItem covering = item() // .id(IMPL, "covering", 1) // .addCoveredId(REQ, "covered", 1) // .build(); @@ -139,10 +141,10 @@ void testDetectOutgoingLinkStatusAmbiguous() @Test void testDetectOutgoingLinkStatusDuplicate() { - final SpecificationItem original = SpecificationItem.builder() // + final SpecificationItem original = item() // .id(REQ, "covered", 1) // .build(); - final SpecificationItem duplicate = SpecificationItem.builder() // + final SpecificationItem duplicate = item() // .id(REQ, "covered", 1) // .build(); final List linkedItems = linkItems(original, duplicate); @@ -154,11 +156,11 @@ void testDetectOutgoingLinkStatusDuplicate() @Test void testDetectIncomingLinkStatusCoveredShallow() { - final SpecificationItem covered = SpecificationItem.builder() // + final SpecificationItem covered = item() // .id(REQ, "covered", 1) // .addNeedsArtifactType(IMPL) // .build(); - final SpecificationItem covering = SpecificationItem.builder() // + final SpecificationItem covering = item() // .id(IMPL, "covering", 1) // .addCoveredId(REQ, "covered", 1) // .build(); @@ -232,10 +234,10 @@ private StringBuilder createDebugInfoFromLinksWithStatus( @Test void testDetectIncomingLinkStatusUnwanted() { - final SpecificationItem covered = SpecificationItem.builder() // + final SpecificationItem covered = item() // .id(REQ, "covered", 1) // .build(); - final SpecificationItem covering = SpecificationItem.builder() // + final SpecificationItem covering = item() // .id(IMPL, "covering", 1) // .addCoveredId(REQ, "covered", 1) // .build(); @@ -251,11 +253,11 @@ void testDetectIncomingLinkStatusUnwanted() @Test void testDetectIncomingLinkStatusCoveredOutdated() { - final SpecificationItem covered = SpecificationItem.builder() // + final SpecificationItem covered = item() // .id(REQ, "newer", 2) // .addNeedsArtifactType(IMPL) // .build(); - final SpecificationItem covering = SpecificationItem.builder() // + final SpecificationItem covering = item() // .id(IMPL, "covering", 1) // .addCoveredId(REQ, "newer", 1) // .build(); @@ -267,11 +269,11 @@ void testDetectIncomingLinkStatusCoveredOutdated() @Test void testDetectIncomingLinkStatusCoveredPredated() { - final SpecificationItem covered = SpecificationItem.builder() // + final SpecificationItem covered = item() // .id(REQ, "older", 1) // .addNeedsArtifactType(IMPL) // .build(); - final SpecificationItem covering = SpecificationItem.builder() // + final SpecificationItem covering = item() // .id(IMPL, "covering", 1) // .addCoveredId(REQ, "older", 2) // .build(); @@ -283,16 +285,16 @@ void testDetectIncomingLinkStatusCoveredPredated() @Test void testCoverageForDifferentArtifactTypes() { - final SpecificationItem covered = SpecificationItem.builder() // + final SpecificationItem covered = item() // .id(REQ, "to-be-covered", 42) // .addNeedsArtifactType(IMPL) // .addNeedsArtifactType(UTEST) // .build(); - final SpecificationItem covering = SpecificationItem.builder() // + final SpecificationItem covering = item() // .id(IMPL, "covering", 1) // .addCoveredId(REQ, "to-be-covered", 42) // .build(); - final SpecificationItem unwanted = SpecificationItem.builder() // + final SpecificationItem unwanted = item() // .id(REQ, "unwanted", 1) // .addCoveredId(REQ, "to-be-covered", 42) // .build(); diff --git a/core/src/test/java/org/itsallcode/openfasttrace/core/TestSpecificationItem.java b/core/src/test/java/org/itsallcode/openfasttrace/core/TestSpecificationItem.java index 24e77b972..d077bcc20 100644 --- a/core/src/test/java/org/itsallcode/openfasttrace/core/TestSpecificationItem.java +++ b/core/src/test/java/org/itsallcode/openfasttrace/core/TestSpecificationItem.java @@ -3,9 +3,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.emptyIterable; - import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.item; import java.util.Arrays; import java.util.List; @@ -57,7 +57,7 @@ void testBuildSimpleSpecificationItem() private SpecificationItem.Builder createSimpleItem() { - final SpecificationItem.Builder builder = SpecificationItem.builder().id(ID); + final SpecificationItem.Builder builder = item().id(ID); builder.title(TITLE).description(DESCRIPTION).rationale(RATIONALE).comment(COMMENT); return builder; } @@ -166,7 +166,7 @@ void testCoverageBuilderWithIdParts() private Builder createTestItemBuilder() { - return SpecificationItem.builder() // + return item() // .id(ARTIFACT_TYPE, NAME, REVISION); } @@ -212,7 +212,7 @@ void testTagBuilder() @Test void testBuildingWithOutIdThrowsExepction() { - final Builder builder = SpecificationItem.builder(); + final Builder builder = item(); assertThrows(IllegalStateException.class, () -> builder.build()); } diff --git a/core/src/test/java/org/itsallcode/openfasttrace/core/TestTracedLink.java b/core/src/test/java/org/itsallcode/openfasttrace/core/TestTracedLink.java index 2da3f8344..607ff2adc 100644 --- a/core/src/test/java/org/itsallcode/openfasttrace/core/TestTracedLink.java +++ b/core/src/test/java/org/itsallcode/openfasttrace/core/TestTracedLink.java @@ -1,8 +1,8 @@ package org.itsallcode.openfasttrace.core; import static org.hamcrest.MatcherAssert.assertThat; - import static org.hamcrest.Matchers.equalTo; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.item; import static org.mockito.Mockito.mock; import org.itsallcode.openfasttrace.api.core.*; @@ -26,7 +26,7 @@ private LinkedSpecificationItem createPrefabricatedLinkedSpecificationItem( final String artifactType, final String name, final int revision) { return new LinkedSpecificationItem( - SpecificationItem.builder().id(artifactType, name, revision).build()); + item().id(artifactType, name, revision).build()); } @Test diff --git a/core/src/test/java/org/itsallcode/openfasttrace/core/TestTracing.java b/core/src/test/java/org/itsallcode/openfasttrace/core/TestTracing.java index 10ed3c5f2..c422bc541 100644 --- a/core/src/test/java/org/itsallcode/openfasttrace/core/TestTracing.java +++ b/core/src/test/java/org/itsallcode/openfasttrace/core/TestTracing.java @@ -2,6 +2,7 @@ import static org.itsallcode.openfasttrace.core.SpecificationItemAssertions.*; import static org.itsallcode.openfasttrace.testutil.core.TraceAssertions.*; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.item; import static org.junit.jupiter.api.Assertions.assertAll; import java.io.IOException; @@ -28,9 +29,9 @@ class TestTracing @BeforeEach void beforeEach() { - this.grandParentBuilder = SpecificationItem.builder().id(grandParentId); - this.parentBuilder = SpecificationItem.builder().id(parentId); - this.childABuilder = SpecificationItem.builder().id(childAId); + this.grandParentBuilder = item().id(grandParentId); + this.parentBuilder = item().id(parentId); + this.childABuilder = item().id(childAId); } // [utest->dsn~tracing.deep-coverage~1] diff --git a/core/src/test/resources/logging.properties b/core/src/test/resources/logging.properties index 727f8e6c3..b0722c144 100644 --- a/core/src/test/resources/logging.properties +++ b/core/src/test/resources/logging.properties @@ -5,6 +5,6 @@ java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.ConsoleHandler.encoding = UTF-8 java.util.logging.SimpleFormatter.format = %1$tF %1$tT [%4$s] %2$s - %5$s %6$s%n -org.itsallcode.openfasttrace.testutil.log.NoOpLoggingHandler.level = FINEST +org.itsallcode.openfasttrace.testutil.log.NoOpLoggingHandler.level = ALL org.itsallcode.openfasttrace.level = FINEST diff --git a/doc/changes/changes.md b/doc/changes/changes.md index 44be7e3e1..fc4e38638 100644 --- a/doc/changes/changes.md +++ b/doc/changes/changes.md @@ -1,6 +1,6 @@ # Changes -* [3.9.0](changes_3.9.0.md) +* [4.0.0](changes_4.0.0.md) * [3.8.0](changes_3.8.0.md) * [3.7.1](changes_3.7.1.md) * [3.7.0](changes_3.7.0.md) diff --git a/doc/changes/changes_3.8.0.md b/doc/changes/changes_3.8.0.md index 5178aa31c..09d576fdb 100644 --- a/doc/changes/changes_3.8.0.md +++ b/doc/changes/changes_3.8.0.md @@ -33,4 +33,4 @@ Now you can also specify a revision for coverage tags instead of the default rev ## Refactoring -* #222: Fixed Java compiler warnings +* #222: Fixed Java compiler warnings \ No newline at end of file diff --git a/doc/changes/changes_4.0.0.md b/doc/changes/changes_4.0.0.md new file mode 100644 index 000000000..1a47df9f2 --- /dev/null +++ b/doc/changes/changes_4.0.0.md @@ -0,0 +1,50 @@ +# OpenFastTrace 4.0.0, released 2024-04-?? + +Code name: RST Importer + +## Summary + +Good news for our 🐍 Python friends: in this release we derived a parser for Restructured Text (RST) from our existing Markdown parser. + +The Markdown parser in the process now accepts specification item titles underlined with either "=" (H1) or "-" (H2). + +Also, the Markdown parser now ignores whitespace in `Needs` and `Tags` entries and correctly parses `Tags` at the start of a requirement item. + +The test coverage for the Markdown importer is now at 100%, and we were able to remove tests that were done with mocks in favor or more robust low-level integration tests. + +We also improved detection of requirement forwarding markup: + + currentArtifactType-->forwaredTo:originalArtifactType~name~revision + +This now works directly after: + +1. Markdown title +2. "Needs" section +3. "Depends" section +4. "Covers" section +5. "Tags" section + +### Breaking Changes + +### Java 17 + +We dropped support for Java 11 for a couple of reasons: + +1. Oracle JDK does not support version 11 anymore +2. AdoptJDK will likely end support for version 11 in September 2024 +3. Most machines now come preinstalled with 17 or later +4. Java 17 has a couple of features that allow for cleaner, safer and more readable code + +### Removed Deprecated Elektrobit-proprietary Specification Item ID Format + +Support for the previously deprecated Elektrobit-proprietary specification item ID format + + req:name, v1 + +has now been removed from the Markdown format. Please migrate to OFT's native format: + + req~name~1 + +## Features + +* #378: Added an RST importer diff --git a/doc/spec/design.md b/doc/spec/design.md index 4ede5f176..81333ade9 100644 --- a/doc/spec/design.md +++ b/doc/spec/design.md @@ -652,10 +652,10 @@ Covers: Needs: impl, utest -#### Markdown Compact "Needs" List -`dsn~md.needs-coverage-list-compact~1` +#### Markdown "Needs" List +`dsn~md.needs-coverage-list~1` -The Markdown Importer supports the following compact format for defining the list of artifact types that are needed to fully cover the current specification item. +The Markdown Importer supports the following list format for defining the list of artifact types that are needed to fully cover the current specification item. needs-list = needs-header 1*(LINEBREAK depends-line) @@ -673,34 +673,8 @@ Covers: Needs: impl, utest -#### Markdown Artifact Forwarding Notation -`dsn~md.artifact-forwarding-notation~1` - -The Markdown Importer supports forwarding required coverage from one artifact type to one or more different artifact types using the following notation. - - artifact-need-redirection = skipped-artifact-type *WSP "-->" *WSP target-artifact-list - *WSP ":" *WSP original-requirement-id - - skipped-artifact-type = artifact-type - - target-artifact-list = artifact-type *("," *WSP artifact-type) - - original-requirement-id = requirement-id - -The following example shows an architectural specification item that forwards the needed coverage directly to the detailed design and an integration test. - - arch --> dsn, itest : req~skip-this-requirement~1 - -Covers: - -* `req~artifact-type-forwarding-in-markdown~1` - -Needs: impl, utest - -### Elektrobit Markdown-style Structures - #### Markdown "Needs" List -`dsn~md.needs-coverage-list~2` +`dsn~md.needs-coverage-list-single-line~2` The Markdown Importer supports the following format for defining the list of artifact types that are needed to fully cover the current specification item. @@ -708,34 +682,35 @@ The Markdown Importer supports the following format for defining the list of art Rationale: -Unlike the the references to other requirements, tags are usually very short, so it is visually beneficial to use a compact style with a comma separated list in a single line. +Unlike references to other requirements, artifact types are usually very short, so it is visually beneficial to use a compact style with a comma separated list in a single line. Covers: -* `req~eb-markdown~1` +* `req~markdown-standard-syntax~1` Needs: impl, utest -#### Legacy Markdown Specification Item ID Format -`dsn~md.eb-markdown-id~1` - -Alternatively a Markdown requirement ID can have the following format - - requirement-id = *1(type~)type ":" id "," *WSP "v" revision - -See [`dsn~md.specification-item-id-format~3`](#markdown-specification-item-id-format) for definitions of the ABNF sub-rules referred to here. - -Rationale: +#### Markdown Artifact Forwarding Notation +`dsn~md.artifact-forwarding-notation~1` -This ID format is supported for backwards compatibility with Elektrobit's legacy requirement-enhanced Markdown format. +The Markdown Importer supports forwarding required coverage from one artifact type to one or more different artifact types using the following notation. -Comment: + artifact-need-redirection = skipped-artifact-type *WSP "-->" *WSP target-artifact-list + *WSP ":" *WSP original-requirement-id + + skipped-artifact-type = artifact-type + + target-artifact-list = artifact-type *("," *WSP artifact-type) + + original-requirement-id = requirement-id -This format is deprecated. Please use the one specified in [`dsn~md.specification-item-id-format~3`](#markdown-specification-item-id-format) for new documents. +The following example shows an architectural specification item that forwards the needed coverage directly to the detailed design and an integration test: + arch --> dsn, itest : req~skip-this-requirement~1 + Covers: -* `req~eb-markdown~1` +* `req~artifact-type-forwarding-in-markdown~1` Needs: impl, utest diff --git a/doc/spec/system_requirements.md b/doc/spec/system_requirements.md index d5ced851f..118d10eb5 100644 --- a/doc/spec/system_requirements.md +++ b/doc/spec/system_requirements.md @@ -249,7 +249,7 @@ For backward compatibility OFT supports a variant of this format that was introd ##### Markdown Standard Syntax `req~markdown-standard-syntax~1` -The OFT Markdown specification artifact format uses the standard markdown syntax without proprietary extensions. +The OFT Markdown specification artifact format uses the standard Markdown syntax without proprietary extensions. Rationale: @@ -276,21 +276,6 @@ Covers: Needs: dsn -##### Support for EB Markdown Requirements -`req~eb-markdown~1` - -In addition to OFT's requirement-enhanced Markdown syntax OFT also supports Elektrobit's variant. - -Rationale: - -This allows stepwise migration to the OFT standard format. The Elektrobit format is a little bit closer to ReqM2. - -Covers: - -* [feat~markdown-import~1](#markdown-import) - -Needs: dsn - ##### Artifact Type Forwarding in Markdown `req~artifact-type-forwarding-in-markdown~1` diff --git a/doc/user_guide.md b/doc/user_guide.md index 22b504e19..af1599b2e 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -330,6 +330,36 @@ In the following example a requirement in the system requirement specification ( arch --> dsn : req~web-ui-uses-corporate-design~1 +Please note that the arrow is intentionally done with two dashes (`-->`) in order to reduce the chance for parsing collisions since the arrow with one dash often appears in documents. + +This notation can appear after: + +* A title +* "Needs" section +* "Depends" section +* "Covers" section +* "Tags" section + +If it appears in a mutli-line text section of a requirement (description, comment or rationale) the forward is ignored. + +Note that a forward terminates the previous specification item, so the following notation does not work: + + `dsn~foo~1` + … + Covers: req~foo~1 + + dsn-->impl:req~bar~1 <-- this terminates the previous specification item + + Needs: impl,utest <-- this is now lost + +To avoid confusion, it is best to have all forwards in a separate section with their own title: + + # Forwarded Requirements + + * `dsn-->impl:req~bar~1` + * `dsn-->impl:req~zoo~2` + * `…` + ### Distributing the Detailing Work In projects of a certain size you always reach the point where a single team is not enough to process the workload. As a consequence the teams must find a way to distribute the work. A popular approach is splitting the architecture into components that are as independent as possible. Each team is then responsible for one or more distinct components. While the act of assigning the work should never be done inside the specification, at least the specification can prepare criteria on which to split the work. diff --git a/exporter/common/.settings/org.eclipse.jdt.core.prefs b/exporter/common/.settings/org.eclipse.jdt.core.prefs index 04b36440c..a40522564 100644 --- a/exporter/common/.settings/org.eclipse.jdt.core.prefs +++ b/exporter/common/.settings/org.eclipse.jdt.core.prefs @@ -1,6 +1,6 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning @@ -20,7 +20,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/exporter/specobject/.settings/org.eclipse.jdt.core.prefs b/exporter/specobject/.settings/org.eclipse.jdt.core.prefs index 04b36440c..a40522564 100644 --- a/exporter/specobject/.settings/org.eclipse.jdt.core.prefs +++ b/exporter/specobject/.settings/org.eclipse.jdt.core.prefs @@ -1,6 +1,6 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning @@ -20,7 +20,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/exporter/specobject/src/test/java/org/itsallcode/openfasttrace/exporter/specobject/TestSpecobjectExporter.java b/exporter/specobject/src/test/java/org/itsallcode/openfasttrace/exporter/specobject/TestSpecobjectExporter.java index da2f09dcf..17505358b 100644 --- a/exporter/specobject/src/test/java/org/itsallcode/openfasttrace/exporter/specobject/TestSpecobjectExporter.java +++ b/exporter/specobject/src/test/java/org/itsallcode/openfasttrace/exporter/specobject/TestSpecobjectExporter.java @@ -3,6 +3,7 @@ import static java.util.Arrays.asList; import static org.hamcrest.MatcherAssert.assertThat; import static org.itsallcode.openfasttrace.testutil.matcher.MultilineTextMatcher.matchesAllLines; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.item; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -23,7 +24,7 @@ class TestSpecobjectExporter @Test void testExportSimpleSpecObjectWithMandatoryElements() throws IOException, XMLStreamException { - final SpecificationItem item = SpecificationItem.builder() // + final SpecificationItem item = item() // .id(SpecificationItemId.createId("foo", "bar", 1)) // .description("the description") // .build(); @@ -45,7 +46,7 @@ void testExportSimpleSpecObjectWithMandatoryElements() throws IOException, XMLSt @Test void testExportSpecObjectWithOptionalElements() throws IOException, XMLStreamException { - final SpecificationItem item = SpecificationItem.builder() // + final SpecificationItem item = item() // .id(SpecificationItemId.createId("req", "me", 2)) // .title("My item title") // .status(ItemStatus.DRAFT) // @@ -96,14 +97,14 @@ void testExportSpecObjectWithOptionalElements() throws IOException, XMLStreamExc @Test void testExportTwoSpecObjects() throws IOException, XMLStreamException { - final SpecificationItem itemA = SpecificationItem.builder() // + final SpecificationItem itemA = item() // .id(SpecificationItemId.createId("foo", "bar", 1)) // .status(ItemStatus.PROPOSED) // .description("the description") // .rationale("the rationale") // .comment("the comment") // .build(); - final SpecificationItem itemB = SpecificationItem.builder() // + final SpecificationItem itemB = item() // .id(SpecificationItemId.createId("baz", "zoo", 2)) // .status(ItemStatus.REJECTED) // .description("another\ndescription") // diff --git a/exporter/specobject/src/test/java/org/itsallcode/openfasttrace/exporter/specobject/TestSpecobjectExporterFactory.java b/exporter/specobject/src/test/java/org/itsallcode/openfasttrace/exporter/specobject/TestSpecobjectExporterFactory.java index 16a293e0a..c2468c250 100644 --- a/exporter/specobject/src/test/java/org/itsallcode/openfasttrace/exporter/specobject/TestSpecobjectExporterFactory.java +++ b/exporter/specobject/src/test/java/org/itsallcode/openfasttrace/exporter/specobject/TestSpecobjectExporterFactory.java @@ -2,6 +2,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.item; import java.io.StringWriter; import java.util.stream.Stream; @@ -26,7 +27,7 @@ void setUp() void testCreateExporterWriterStreamOfSpecificationItemNewline() { final StringWriter writer = new StringWriter(); - final SpecificationItem item = SpecificationItem.builder().id("art", "name", 42).build(); + final SpecificationItem item = item().id("art", "name", 42).build(); final Exporter exporter = factory.createExporter(writer, Stream.of(item), Newline.UNIX); exporter.runExport(); diff --git a/importer/lightweightmarkup/.settings/org.eclipse.jdt.core.prefs b/importer/lightweightmarkup/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..a40522564 --- /dev/null +++ b/importer/lightweightmarkup/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,413 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=17 +org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false +org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false +org.eclipse.jdt.core.formatter.align_with_spaces=false +org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assertion_message=0 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 +org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_record_components=16 +org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_type_annotations=0 +org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=next_line_on_wrap +org.eclipse.jdt.core.formatter.brace_position_for_block=next_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=next_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=next_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=next_line +org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=next_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line +org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false +org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.indent_tag_description=false +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=false +org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=true +org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false +org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=space +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.text_block_indentation=0 +org.eclipse.jdt.core.formatter.use_on_off_tags=true +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true +org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true +org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true +org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true +org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true +org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true +org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/importer/lightweightmarkup/.settings/org.eclipse.jdt.ui.prefs b/importer/lightweightmarkup/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..0a69ece30 --- /dev/null +++ b/importer/lightweightmarkup/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,146 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_itsallcode style +formatter_settings_version=21 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=4 +org.eclipse.jdt.ui.staticondemandthreshold=4 +sp_cleanup.add_all=false +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.array_with_curly=false +sp_cleanup.arrays_fill=false +sp_cleanup.bitwise_conditional_expression=false +sp_cleanup.boolean_literal=false +sp_cleanup.boolean_value_rather_than_comparison=false +sp_cleanup.break_loop=false +sp_cleanup.collection_cloning=false +sp_cleanup.comparing_on_criteria=false +sp_cleanup.comparison_statement=false +sp_cleanup.controlflow_merge=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false +sp_cleanup.convert_to_switch_expressions=false +sp_cleanup.correct_indentation=false +sp_cleanup.do_while_rather_than_while=false +sp_cleanup.double_negation=false +sp_cleanup.else_if=false +sp_cleanup.embedded_if=false +sp_cleanup.evaluate_nullable=false +sp_cleanup.extract_increment=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.hash=false +sp_cleanup.if_condition=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.instanceof=false +sp_cleanup.instanceof_keyword=false +sp_cleanup.invert_equals=false +sp_cleanup.join=false +sp_cleanup.lazy_logical_operator=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.map_cloning=false +sp_cleanup.merge_conditional_blocks=false +sp_cleanup.multi_catch=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.no_string_creation=false +sp_cleanup.no_super=false +sp_cleanup.number_suffix=false +sp_cleanup.objects_equals=false +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false +sp_cleanup.operand_factorization=false +sp_cleanup.organize_imports=true +sp_cleanup.overridden_assignment=false +sp_cleanup.plain_replacement=false +sp_cleanup.precompile_regex=false +sp_cleanup.primitive_comparison=false +sp_cleanup.primitive_parsing=false +sp_cleanup.primitive_rather_than_wrapper=false +sp_cleanup.primitive_serialization=false +sp_cleanup.pull_out_if_from_if_else=false +sp_cleanup.pull_up_assignment=false +sp_cleanup.push_down_negation=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.reduce_indentation=false +sp_cleanup.redundant_comparator=false +sp_cleanup.redundant_falling_through_block_end=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_modifiers=false +sp_cleanup.remove_redundant_semicolons=false +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_array_creation=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.return_expression=false +sp_cleanup.simplify_lambda_expression_and_method_ref=false +sp_cleanup.single_used_field=false +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.standard_comparison=false +sp_cleanup.static_inner_class=false +sp_cleanup.strictly_equal_or_different=false +sp_cleanup.stringbuffer_to_stringbuilder=false +sp_cleanup.stringbuilder=false +sp_cleanup.stringbuilder_for_local_vars=false +sp_cleanup.stringconcat_to_textblock=false +sp_cleanup.substring=false +sp_cleanup.switch=false +sp_cleanup.system_property=false +sp_cleanup.system_property_boolean=false +sp_cleanup.system_property_file_encoding=false +sp_cleanup.system_property_file_separator=false +sp_cleanup.system_property_line_separator=false +sp_cleanup.system_property_path_separator=false +sp_cleanup.ternary_operator=false +sp_cleanup.try_with_resource=false +sp_cleanup.unlooped_while=false +sp_cleanup.unreachable_block=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_autoboxing=false +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_directly_map_method=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_string_is_blank=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true +sp_cleanup.use_unboxing=false +sp_cleanup.use_var=false +sp_cleanup.useless_continue=false +sp_cleanup.useless_return=false +sp_cleanup.valueof_rather_than_instantiation=false diff --git a/importer/lightweightmarkup/pom.xml b/importer/lightweightmarkup/pom.xml new file mode 100644 index 000000000..588723072 --- /dev/null +++ b/importer/lightweightmarkup/pom.xml @@ -0,0 +1,32 @@ + + 4.0.0 + openfasttrace-importer-lightweightmarkup + OpenFastTrace Lightweight Markup Importer Base + + ../../parent/pom.xml + org.itsallcode.openfasttrace + openfasttrace-parent + ${revision} + + + ${reproducible.build.timestamp} + + + + org.itsallcode.openfasttrace + openfasttrace-api + + + org.itsallcode.openfasttrace + openfasttrace-testutil + test + + + com.jparams + to-string-verifier + test + + + diff --git a/importer/lightweightmarkup/src/main/java/module-info.java b/importer/lightweightmarkup/src/main/java/module-info.java new file mode 100644 index 000000000..5a7b76674 --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/module-info.java @@ -0,0 +1,12 @@ +/** + * Base module for importers of lightweight markup formats. + */ +module org.itsallcode.openfasttrace.importer.lightweightmarkup +{ + exports org.itsallcode.openfasttrace.importer.lightweightmarkup; + exports org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine; + exports org.itsallcode.openfasttrace.importer.lightweightmarkup.linereader; + + requires java.logging; + requires transitive org.itsallcode.openfasttrace.api; +} diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownForwardingSpecificationItem.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/ForwardingSpecificationItem.java similarity index 76% rename from importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownForwardingSpecificationItem.java rename to importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/ForwardingSpecificationItem.java index 05acca358..eb9306ce6 100644 --- a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownForwardingSpecificationItem.java +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/ForwardingSpecificationItem.java @@ -1,4 +1,4 @@ -package org.itsallcode.openfasttrace.importer.markdown; +package org.itsallcode.openfasttrace.importer.lightweightmarkup; import java.util.Arrays; import java.util.List; @@ -6,26 +6,30 @@ import org.itsallcode.openfasttrace.api.core.SpecificationItemId; /** - * The {@link MarkdownForwardingSpecificationItem} splits the textual + * The {@link ForwardingSpecificationItem} splits the textual * representation of a specification item that forwards needed artifact coverage * into is components. */ -public class MarkdownForwardingSpecificationItem +public class ForwardingSpecificationItem { - static final String ORIGINAL_MARKER = ":"; - static final String FORWARD_MARKER = "-->"; + // The following markers are part of the syntax of a forward statement. They are public because they are required + // in different parsers. + /** Marker after which we expect the original artifact type. */ + public static final String ORIGINAL_MARKER = ":"; + /** Marker after which the artifact types are listed to which we forward. */ + public static final String FORWARD_MARKER = "-->"; private final String skippedArtifactType; private final SpecificationItemId originalId; private final SpecificationItemId skippedId; private final List targetArtifactTypes; /** - * Create an instance of {@link MarkdownForwardingSpecificationItem} + * Create an instance of {@link ForwardingSpecificationItem} * * @param forward * the textual representation */ - public MarkdownForwardingSpecificationItem(final String forward) + public ForwardingSpecificationItem(final String forward) { final int posForwardMarker = forward.indexOf(FORWARD_MARKER); final int posOriginalMarker = forward.indexOf(ORIGINAL_MARKER); diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/LightWeightMarkupImporter.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/LightWeightMarkupImporter.java new file mode 100644 index 000000000..41bc69674 --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/LightWeightMarkupImporter.java @@ -0,0 +1,282 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup; + +import org.itsallcode.openfasttrace.api.core.ItemStatus; +import org.itsallcode.openfasttrace.api.core.SpecificationItemId; +import org.itsallcode.openfasttrace.api.importer.ImportEventListener; +import org.itsallcode.openfasttrace.api.importer.Importer; +import org.itsallcode.openfasttrace.api.importer.input.InputFile; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.linereader.*; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.*; + +/** + * Base class for importers of lightweight markup text. + */ +public abstract class LightWeightMarkupImporter implements Importer, LineReaderCallback +{ + /** File to be imported */ + protected final InputFile file; + /** Listener for import events */ + protected final ImportEventListener listener; + /** State machine for a line-by-line parser */ + protected final LineParserStateMachine stateMachine; + private String lastTitle; + private boolean inSpecificationItem; + private LineContext currentContext; + + /** + * Create a new {@link LightWeightMarkupImporter}. + * + * @param file + * input file + * @param listener + * import event listener + */ + // Possible 'this' escape before subclass is fully initialized: + // LineParserStateMachine constructor does not use 'this'. + @SuppressWarnings("this-escape") + protected LightWeightMarkupImporter(final InputFile file, final ImportEventListener listener) + { + this.file = file; + this.listener = listener; + this.stateMachine = new LineParserStateMachine(configureTransitions()); + } + + @Override + public void runImport() + { + new LineReader(file, this).readFile(); + } + + /** + * Define the transitions of the parser statemachine. + * + * @return parser statemachine transitions + */ + protected abstract Transition[] configureTransitions(); + + @Override + public void nextLine(final LineContext context) + { + this.currentContext = context; + this.stateMachine.step(this.currentContext.currentLine(), this.currentContext.nextLine()); + } + + /** + * Define a transition in the parser statemachine. + * + * @param from + * state to be matched against the parsers current state + * @param to + * state the parser will be in if the transition happened + * @param pattern + * line pattern to be matched for this transition to happen + * @param action + * action to take as during the transition + * @return transition definition + */ + protected static Transition transition(final LineParserState from, final LineParserState to, + final LinePattern pattern, final TransitionAction action) + { + return new Transition(from, to, pattern, action); + } + + @Override + public void finishReading() + { + if (this.inSpecificationItem) + { + this.listener.endSpecificationItem(); + } + } + + /** + * Start a new specification item. + */ + protected void beginItem() + { + cleanUpLastItem(); + this.inSpecificationItem = true; + informListenerAboutNewItem(); + } + + /** + * Force the end of an open specification item. + */ + protected void cleanUpLastItem() + { + if (this.inSpecificationItem) + { + endItem(); + } + } + + /** + * Informs the listener about a new specification item, including the ID and + * file and line where it was detected. + */ + protected void informListenerAboutNewItem() + { + final String idText = this.stateMachine.getLastToken(); + final SpecificationItemId id = new SpecificationItemId.Builder(idText).build(); + this.listener.beginSpecificationItem(); + this.listener.setId(id); + this.listener.setLocation(this.file.getPath(), this.currentContext.lineNumber()); + if (this.lastTitle != null) + { + this.listener.setTitle(this.lastTitle); + } + } + + /** + * End a specification item gracefully. + *

+ * As opposed to forcing an end at clean-up (see + * {@link LightWeightMarkupImporter#cleanUpLastItem()}. + *

+ */ + protected void endItem() + { + this.inSpecificationItem = false; + resetTitle(); + this.listener.endSpecificationItem(); + } + + /** + * Set the specification item status. + */ + protected void setStatus() + { + this.listener.setStatus(ItemStatus.parseString(this.stateMachine.getLastToken())); + } + + /** + * Begin the textual description of the specification item. + */ + protected void beginDescription() + { + this.listener.appendDescription(this.stateMachine.getLastToken()); + } + + /** + * Append text to an existing piece of the specification item description. + */ + protected void appendDescription() + { + this.listener.appendDescription(System.lineSeparator()); + this.listener.appendDescription(this.stateMachine.getLastToken()); + } + + /** + * Begin the rationale. + */ + protected void beginRationale() + { + this.listener.appendRationale(System.lineSeparator()); + } + + /** + * Append text to an existing piece of the rationale. + */ + protected void appendRationale() + { + this.listener.appendRationale(System.lineSeparator()); + this.listener.appendRationale(this.stateMachine.getLastToken()); + } + + /** + * Begin a comment. + */ + protected void beginComment() + { + this.listener.appendComment(this.stateMachine.getLastToken()); + } + + /** + * Append text to an existing piece of the comment. + */ + protected void appendComment() + { + this.listener.appendComment(System.lineSeparator()); + this.listener.appendComment(this.stateMachine.getLastToken()); + } + + /** + * Add a dependency on another specification item by ID. + */ + protected void addDependency() + { + final SpecificationItemId.Builder builder = new SpecificationItemId.Builder( + this.stateMachine.getLastToken()); + this.listener.addDependsOnId(builder.build()); + } + + /** + * Add artifact types that this specification item needs to be covered in. + */ + protected void addNeeds() + { + final String artifactTypes = this.stateMachine.getLastToken(); + for (final String artifactType : artifactTypes.split(",")) + { + this.listener.addNeededArtifactType(artifactType.trim()); + } + } + + /** + * Remember the last section title in case this turns out to be a + * specification item. + */ + // [impl->dsn~md.specification-item-title~1] + protected void rememberTitle() + { + this.lastTitle = this.stateMachine.getLastToken(); + } + + /** + * Reset the stored section title. + */ + protected void resetTitle() + { + this.lastTitle = null; + } + + /** + * Add an ID for a specification item this one covers. + */ + protected void addCoverage() + { + this.listener.addCoveredId(SpecificationItemId.parseId(this.stateMachine.getLastToken())); + } + + /** + * Add one or more tags. + */ + protected void addTag() + { + final String tags = this.stateMachine.getLastToken(); + for (final String tag : tags.split(",")) + { + this.listener.addTag(tag.trim()); + } + } + + /** + * Create a specification item from a forward marker. + */ + // [impl->dsn~md.artifact-forwarding-notation~1] + protected void forward() + { + final ForwardingSpecificationItem forward = new ForwardingSpecificationItem( + this.stateMachine.getLastToken()); + this.listener.beginSpecificationItem(); + this.listener.setId(forward.getSkippedId()); + this.listener.addCoveredId(forward.getOriginalId()); + for (final String targetArtifactType : forward.getTargetArtifactTypes()) + { + this.listener.addNeededArtifactType(targetArtifactType.trim()); + } + this.listener.setForwards(true); + this.listener.setLocation(this.file.getPath(), this.currentContext.lineNumber()); + this.listener.endSpecificationItem(); + } +} diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineContext.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineContext.java new file mode 100644 index 000000000..3151259de --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineContext.java @@ -0,0 +1,19 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.linereader; + +/** + * State of the {@link LineReader} at a given line. + * + * @param lineNumber + * the current line number, starting with 1 for the first line + * @param previousLine + * the previous line or {@code null} if the current line is the first + * line + * @param currentLine + * the current line, never {@code null} + * @param nextLine + * the next line or {@code null} if the current line is the last line + */ +public record LineContext(int lineNumber, String previousLine, String currentLine, String nextLine) +{ + +} diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineReader.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineReader.java new file mode 100644 index 000000000..1f1664364 --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineReader.java @@ -0,0 +1,74 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.linereader; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.logging.Logger; + +import org.itsallcode.openfasttrace.api.importer.ImporterException; +import org.itsallcode.openfasttrace.api.importer.input.InputFile; + +/** + * Read a file line by line and call a callback for each line. + */ +public class LineReader +{ + private static final Logger LOG = Logger.getLogger(LineReader.class.getName()); + + private final InputFile file; + private final LineReaderCallback callback; + + /** + * Create a new {@link LineReader}. + * + * @param file + * the file to read + * @param callback + * the callback to call for each line + */ + public LineReader(final InputFile file, final LineReaderCallback callback) + { + this.file = file; + this.callback = callback; + } + + /** + * Start reading the file and call + * {@link LineReaderCallback#nextLine(LineContext)} for each line. After + * reading the last line, this will call + * {@link LineReaderCallback#finishReading()}. + */ + public void readFile() + { + LOG.finest(() -> "Starting import of file '" + this.file + "'"); + String previousLine = null; + String currentLine = null; + String nextLine = null; + int lineNumber = 0; + try (BufferedReader reader = this.file.createReader()) + { + while ((nextLine = reader.readLine()) != null) + { + if (currentLine != null) + { + callback.nextLine(new LineContext(lineNumber, previousLine, currentLine, nextLine)); + } + ++lineNumber; + previousLine = currentLine; + currentLine = nextLine; + } + if (currentLine != null) + { + callback.nextLine(new LineContext(lineNumber, previousLine, currentLine, nextLine)); + } + } + catch (final IOException exception) + { + throw new ImporterException( + "Error reading '" + this.file.getPath() + "' at line " + lineNumber + ": " + + exception.getMessage(), + exception); + + } + callback.finishReading(); + } +} diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineReaderCallback.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineReaderCallback.java new file mode 100644 index 000000000..e9697a27a --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineReaderCallback.java @@ -0,0 +1,21 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.linereader; + +/** + * Callback for the {@link LineReader} to notify the caller about the lines that + * have been read. + */ +public interface LineReaderCallback +{ + /** + * Notify the caller about the next line that has been read. + * + * @param context + * contains the current line and the surrounding lines + */ + void nextLine(LineContext context); + + /** + * Notify the caller that the file has been read completely. + */ + void finishReading(); +} diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserState.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserState.java new file mode 100644 index 000000000..7bd3cc73b --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserState.java @@ -0,0 +1,34 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine; + +/** + * This enum defines the state the line parser for lightweight markup languages + * can be in. + */ +public enum LineParserState +{ + /** + * Parser started (at beginning of the file) or outside of a specification + * item + */ + START, + /** Inside a specification item */ + SPEC_ITEM, + /** Inside a description section */ + DESCRIPTION, + /** Inside a provided coverage section */ + COVERS, + /** Inside a section describing dependencies */ + DEPENDS, + /** Inside a rationale section */ + RATIONALE, + /** Inside a comment section */ + COMMENT, + /** Inside a section defining the required coverage */ + NEEDS, + /** Found a title */ + TITLE, + /** Found tags */ + TAGS, + /** Reached the end of the file */ + EOF +} diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserStateMachine.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserStateMachine.java new file mode 100644 index 000000000..853437988 --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserStateMachine.java @@ -0,0 +1,109 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine; + +import java.util.*; +import java.util.logging.Logger; + +/** + * This machine implements the core of a state based parser. + *

+ * Before the state machine is run, it needs to be configured with a transition + * table in the constructor. + *

+ *

+ * Each step of the state machine gets a portion of the text to be imported as + * input. The machine checks the current state and the input on each step and + * decides on resulting state and action depending on the configuration provided + * in the transition table. + *

+ */ +public class LineParserStateMachine +{ + private static final Logger LOG = Logger.getLogger(LineParserStateMachine.class.getName()); + + private LineParserState state = LineParserState.START; + private String lastToken = ""; + private final Transition[] transitions; + + /** + * Create a new instance of the {@link LineParserStateMachine} + * + * @param transitions + * the transition table that serves as configuration for the + * state machine + */ + public LineParserStateMachine(final Transition[] transitions) + { + this.transitions = Arrays.copyOf(transitions, transitions.length); + } + + /** + * Step the state machine. + * + * @param line + * the text fragment on which the state machine decides the next + * state and action + * @param nextLine + * the following line or {@code null} if the current line is the + * last one in the file. This is useful as a lookahead for + * patterns that span multiple lines like underlined titles in + * Markdown or RST. + */ + public void step(final String line, final String nextLine) + { + boolean matched = false; + for (final Transition entry : this.transitions) + { + if ((this.state == entry.getFrom()) && matchToken(line, nextLine, entry)) + { + LOG.finest(() -> entry + " : '" + line + "'"); + entry.getTransitionAction().transit(); + this.state = entry.getTo(); + matched = true; + break; + } + } + if (!matched) + { + LOG.finest(() -> "Current state: " + this.state + ", no match for '" + line + "'"); + } + } + + private boolean matchToken(final String line, final String nextLine, final Transition entry) + { + final Optional> matches = entry.getLinePattern().getMatches(line, nextLine); + if (matches.isPresent()) + { + final List groups = matches.get(); + this.lastToken = groups.isEmpty() ? "" : groups.get(0); + return true; + } + else + { + this.lastToken = ""; + return false; + } + } + + /** + * Get the last text token that the state machine isolated + * + * @return the last text token + */ + public String getLastToken() + { + return this.lastToken; + } + + /** + * Get the current state of the state machine. + *

+ * This method is package private because it used only for testing. + *

+ * + * @return the current state of the state machine + */ + LineParserState getState() + { + return this.state; + } +} diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LinePattern.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LinePattern.java new file mode 100644 index 000000000..23968c63e --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LinePattern.java @@ -0,0 +1,28 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine; + +import java.util.List; +import java.util.Optional; + +/** + * Common interface for text patterns used in line parsers. + */ +public interface LinePattern +{ + /** + * Get the matching groups of the regular expression pattern in the given + * line and its following line. + *

+ * Implementors are free to ignore the following line if it is not needed. + *

+ * + * @param line + * the current line + * @param nextLine + * the following line or {@code null} if the current line is the + * last line + * @return list of matching groups or an empty optional if the pattern does + * not match. If the pattern does not have any groups, the list will + * be empty. + */ + Optional> getMatches(final String line, final String nextLine); +} diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/SimpleLinePattern.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/SimpleLinePattern.java new file mode 100644 index 000000000..d0cbfac6f --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/SimpleLinePattern.java @@ -0,0 +1,48 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Simple {@link LinePattern} implementation that only considers the current + * line and not the following line. + */ +public final class SimpleLinePattern implements LinePattern +{ + private final Pattern pattern; + + private SimpleLinePattern(final Pattern pattern) + { + this.pattern = pattern; + } + + /** + * Create a new instance of a {@link SimpleLinePattern}. + * + * @param pattern + * the regular expression pattern to match, potentially + * containing groups, see {@link Pattern#compile(String)} + * @return a new instance of a {@link SimpleLinePattern} + */ + public static SimpleLinePattern of(final String pattern) + { + return new SimpleLinePattern(Pattern.compile(pattern)); + } + + @Override + public Optional> getMatches(final String line, final String nextLine) + { + final Matcher matcher = pattern.matcher(line); + if (matcher.matches()) + { + final List matches = new ArrayList<>(); + for (int i = 1; i <= matcher.groupCount(); i++) + { + matches.add(matcher.group(i)); + } + return Optional.of(matches); + } + return Optional.empty(); + } +} diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/Transition.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/Transition.java new file mode 100644 index 000000000..8d0f232f0 --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/Transition.java @@ -0,0 +1,81 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine; + +/** + * Transition in the line parser statemachine. + */ +public class Transition +{ + private final LineParserState from; + private final LineParserState to; + private final LinePattern linePattern; + private final TransitionAction transitionAction; + + /** + * Create a new instance of a {@link Transition}. + * + * @param from + * state the statemachine comes from + * @param to + * state the machine will switch to if the pattern matches + * @param linePattern + * pattern the line must match for the transition to happen + * @param transitionAction + * action that will be executed as result of the transition + */ + public Transition(final LineParserState from, final LineParserState to, final LinePattern linePattern, + final TransitionAction transitionAction) + { + this.from = from; + this.to = to; + this.linePattern = linePattern; + this.transitionAction = transitionAction; + } + + /** + * Get the origin state of this transition. + * + * @return origin state + */ + public LineParserState getFrom() + { + return this.from; + } + + /** + * Get the target state of this transition. + * + * @return target state + */ + public LineParserState getTo() + { + return this.to; + } + + /** + * Get the regular expression pattern that needs to be matched in order for + * the transition to happen. + * + * @return line pattern to be matched + */ + public LinePattern getLinePattern() + { + return this.linePattern; + } + + /** + * Get the action that is executed when the transition happens. + * + * @return action that is executed as result of the transition + */ + public TransitionAction getTransitionAction() + { + return this.transitionAction; + } + + @Override + public String toString() + { + return "Transition [from=" + this.from + ", to=" + this.to + ", markdownPattern=" + + this.linePattern + "]"; + } +} diff --git a/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/TransitionAction.java b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/TransitionAction.java new file mode 100644 index 000000000..58d90329a --- /dev/null +++ b/importer/lightweightmarkup/src/main/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/TransitionAction.java @@ -0,0 +1,13 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine; + +/** + * Action that is executed as a result of a state transition in the line parser. + */ +@FunctionalInterface +public interface TransitionAction +{ + /** + * Execute the transition action. + */ + void transit(); +} diff --git a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownForwardingSpecificationItemTest.java b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/ForwardingSpecificationItemTest.java similarity index 78% rename from importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownForwardingSpecificationItemTest.java rename to importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/ForwardingSpecificationItemTest.java index e10084d25..4cb44ee4b 100644 --- a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownForwardingSpecificationItemTest.java +++ b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/ForwardingSpecificationItemTest.java @@ -1,19 +1,19 @@ -package org.itsallcode.openfasttrace.importer.markdown; - -import org.itsallcode.openfasttrace.api.core.SpecificationItemId; -import org.junit.jupiter.api.Test; +package org.itsallcode.openfasttrace.importer.lightweightmarkup; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.itsallcode.openfasttrace.api.core.SpecificationItemId; +import org.junit.jupiter.api.Test; -class MarkdownForwardingSpecificationItemTest +class ForwardingSpecificationItemTest { @Test void parseForwardInstrcution() { - final MarkdownForwardingSpecificationItem item = new MarkdownForwardingSpecificationItem( + final ForwardingSpecificationItem item = new ForwardingSpecificationItem( "arch --> dsn : req~web-ui-uses-corporate-design~1"); assertAll( () -> assertThat(item.getSkippedArtifactType(), equalTo("arch")), @@ -23,4 +23,4 @@ void parseForwardInstrcution() () -> assertThat(item.getOriginalId(), equalTo(SpecificationItemId.parseId("req~web-ui-uses-corporate-design~1")))); } -} \ No newline at end of file +} diff --git a/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/TransitionTest.java b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/TransitionTest.java new file mode 100644 index 000000000..42e8c4753 --- /dev/null +++ b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/TransitionTest.java @@ -0,0 +1,25 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.Mockito.when; + +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class TransitionTest +{ + @Test + void testToString(@Mock final TransitionAction actionMock, @Mock final LinePattern patternMock) + { + when(patternMock.toString()).thenReturn("DUMMY_PATTERN"); + final Transition transition = new Transition(LineParserState.COMMENT, LineParserState.TITLE, patternMock, + actionMock); + assertThat(transition.toString(), + equalTo("Transition [from=COMMENT, to=TITLE, markdownPattern=DUMMY_PATTERN]")); + } +} diff --git a/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineContextTest.java b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineContextTest.java new file mode 100644 index 000000000..d58395f9f --- /dev/null +++ b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineContextTest.java @@ -0,0 +1,22 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.linereader; + +import org.junit.jupiter.api.Test; + +import com.jparams.verifier.tostring.ToStringVerifier; + +import nl.jqno.equalsverifier.EqualsVerifier; + +class LineContextTest +{ + @Test + void testEqualsAndHashContract() + { + EqualsVerifier.forClass(LineContext.class).verify(); + } + + @Test + void testToString() + { + ToStringVerifier.forClass(LineContext.class).verify(); + } +} diff --git a/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineReaderTest.java b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineReaderTest.java new file mode 100644 index 000000000..75f08cf02 --- /dev/null +++ b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/linereader/LineReaderTest.java @@ -0,0 +1,117 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.linereader; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.*; + +import java.io.*; + +import org.itsallcode.openfasttrace.api.importer.ImporterException; +import org.itsallcode.openfasttrace.api.importer.input.InputFile; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class LineReaderTest +{ + private static final String FILE_PATH = "file/path"; + + @Mock + LineReaderCallback callbackMock; + InOrder inOrder; + + @BeforeEach + void setUp() + { + inOrder = inOrder(callbackMock); + } + + @AfterEach + void verifyNoMoreInteractions() + { + inOrder.verifyNoMoreInteractions(); + } + + @ParameterizedTest + @ValueSource(strings = + { "", "\n", "\r\n", "\n\r" }) + void testReadEmptyFile(final String content) throws IOException + { + parse(content); + inOrder.verify(callbackMock).finishReading(); + } + + @ParameterizedTest + @ValueSource(strings = + { "", "\n", "\r\n", }) + void testReadSingleLine(final String lineEnding) throws IOException + { + parse("line1" + lineEnding); + inOrder.verify(callbackMock).nextLine(new LineContext(1, null, "line1", null)); + inOrder.verify(callbackMock).finishReading(); + } + + @ParameterizedTest + @ValueSource(strings = + { "\n", "\r\n" }) + void testReadTwoLines(final String lineEnding) throws IOException + { + parse("line1" + lineEnding + "line2" + lineEnding); + inOrder.verify(callbackMock).nextLine(new LineContext(1, null, "line1", "line2")); + inOrder.verify(callbackMock).nextLine(new LineContext(2, "line1", "line2", null)); + inOrder.verify(callbackMock).finishReading(); + } + + @ParameterizedTest + @ValueSource(strings = + { "\n", "\r\n" }) + void testReadThreeLines(final String lineEnding) throws IOException + { + parse("line1" + lineEnding + "line2" + lineEnding + "line3" + lineEnding); + inOrder.verify(callbackMock).nextLine(new LineContext(1, null, "line1", "line2")); + inOrder.verify(callbackMock).nextLine(new LineContext(2, "line1", "line2", "line3")); + inOrder.verify(callbackMock).nextLine(new LineContext(3, "line2", "line3", null)); + inOrder.verify(callbackMock).finishReading(); + } + + @ParameterizedTest + @ValueSource(strings = + { "\n", "\r\n" }) + void testReadFourLines(final String lineEnding) throws IOException + { + parse("line1" + lineEnding + "line2" + lineEnding + "line3" + lineEnding + "line4"); + inOrder.verify(callbackMock).nextLine(new LineContext(1, null, "line1", "line2")); + inOrder.verify(callbackMock).nextLine(new LineContext(2, "line1", "line2", "line3")); + inOrder.verify(callbackMock).nextLine(new LineContext(3, "line2", "line3", "line4")); + inOrder.verify(callbackMock).nextLine(new LineContext(4, "line3", "line4", null)); + inOrder.verify(callbackMock).finishReading(); + } + + @Test + void testReadFails(@Mock final BufferedReader readerMock) throws IOException + { + when(readerMock.readLine()).thenThrow(new IOException("mock")); + final ImporterException exception = assertThrows(ImporterException.class, () -> parse(readerMock)); + assertThat(exception.getMessage(), equalTo("Error reading '" + FILE_PATH + "' at line 0: mock")); + } + + private void parse(final String content) throws IOException + { + final BufferedReader reader = new BufferedReader(new StringReader(content)); + parse(reader); + } + + private void parse(final BufferedReader reader) throws IOException + { + final InputFile inputFileMock = mock(InputFile.class); + lenient().when(inputFileMock.getPath()).thenReturn(FILE_PATH); + when(inputFileMock.createReader()).thenReturn(reader); + new LineReader(inputFileMock, callbackMock).readFile(); + } +} diff --git a/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserStateMachineTest.java b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserStateMachineTest.java new file mode 100644 index 000000000..1d31ca025 --- /dev/null +++ b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/LineParserStateMachineTest.java @@ -0,0 +1,121 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.*; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class LineParserStateMachineTest +{ + LineParserStateMachine stateMachine; + @Mock + TransitionAction actionMock; + + @Test + void testNoTransitions() + { + setupTransitions(); + step("line"); + assertTransition(LineParserState.START, ""); + verifyNoMoreInteractions(actionMock); + } + + private void setupTransitions(final Transition... transitions) + { + this.stateMachine = new LineParserStateMachine(transitions); + } + + private void assertTransition(final LineParserState expectedState, final String expectedToken) + { + assertAll(() -> assertThat("next state", this.stateMachine.getState(), equalTo(expectedState)), + () -> assertThat("token", this.stateMachine.getLastToken(), equalTo(expectedToken))); + } + + private void step(final String inputLine) + { + this.step(inputLine, null); + } + + private void step(final String inputLine, final String nextInputLine) + { + this.stateMachine.step(inputLine, nextInputLine); + } + + @Test + void testMatchedSingleTransitionWithMock(@Mock final LinePattern patternMock) + { + when(patternMock.getMatches("line1", "line2")).thenReturn(Optional.of(List.of("result", "ignored"))); + setupTransitions(transition(LineParserState.START, LineParserState.COMMENT, patternMock)); + step("line1", "line2"); + assertTransition(LineParserState.COMMENT, "result"); + verify(actionMock).transit(); + verifyNoMoreInteractions(actionMock); + } + + @Test + void testNotMatchedTransitionWithMock(@Mock final LinePattern patternMock) + { + when(patternMock.getMatches("line1", "line2")).thenReturn(Optional.empty()); + setupTransitions(transition(LineParserState.START, LineParserState.COMMENT, patternMock)); + step("line1", "line2"); + assertTransition(LineParserState.START, ""); + verifyNoMoreInteractions(actionMock); + } + + @Test + void testMatchedTransitionInNextLine() + { + setupTransitions(transition(LineParserState.START, LineParserState.COMMENT, pattern("(line)"))); + step("line"); + assertTransition(LineParserState.COMMENT, "line"); + verify(actionMock).transit(); + verifyNoMoreInteractions(actionMock); + } + + @Test + void testMatchedTransitionNoToken() + { + setupTransitions(transition(LineParserState.START, LineParserState.COMMENT, pattern("line"))); + step("line"); + assertTransition(LineParserState.COMMENT, ""); + verify(actionMock).transit(); + verifyNoMoreInteractions(actionMock); + } + + @Test + void testWrongState() + { + setupTransitions(transition(LineParserState.COMMENT, LineParserState.COMMENT, pattern("line"))); + step("line"); + assertTransition(LineParserState.START, ""); + verifyNoMoreInteractions(actionMock); + } + + @Test + void testNoMatch() + { + setupTransitions(transition(LineParserState.START, LineParserState.COMMENT, pattern("notmatching"))); + step("line"); + assertTransition(LineParserState.START, ""); + verifyNoMoreInteractions(actionMock); + } + + private LinePattern pattern(final String pattern) + { + return SimpleLinePattern.of(pattern); + } + + private Transition transition(final LineParserState from, final LineParserState to, final LinePattern pattern) + { + return new Transition(from, to, pattern, actionMock); + } +} diff --git a/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/SimpleLinePatternTest.java b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/SimpleLinePatternTest.java new file mode 100644 index 000000000..29ee249ed --- /dev/null +++ b/importer/lightweightmarkup/src/test/java/org/itsallcode/openfasttrace/importer/lightweightmarkup/statemachine/SimpleLinePatternTest.java @@ -0,0 +1,53 @@ +package org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.itsallcode.matcher.auto.AutoMatcher.equalTo; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class SimpleLinePatternTest +{ + static Stream testCases() + { + return Stream.of( + testCase("abc", "abc", List.of()), + testCase("abc", "def", null), + testCase("([0-9]+)", "123", List.of("123")), + testCase("([0-9]+)(\\w+)", "123abc", List.of("123", "abc")), + testCase("(?:[0-9]+)", "123", List.of()), + testCase("[0-9]+", "123", List.of()), + testCase("([0-9]+)", "abc", null)); + } + + private static Arguments testCase(final String pattern, final String line, final List expected) + { + return Arguments.of(pattern, line, expected); + } + + @ParameterizedTest + @MethodSource("testCases") + void test(final String pattern, final String line, final List expected) + { + final Optional> matches = SimpleLinePattern.of(pattern).getMatches(line, null); + if (expected == null) + { + assertThat("Text '" + line + "' should not match pattern '" + pattern + "'", matches.isPresent(), + is(false)); + } + else + { + assertAll( + () -> assertThat("Text '" + line + "' should match pattern '" + pattern + "'", matches.isPresent(), + is(true)), + () -> assertThat(matches.get(), equalTo(expected))); + } + } +} diff --git a/importer/markdown/.settings/org.eclipse.jdt.core.prefs b/importer/markdown/.settings/org.eclipse.jdt.core.prefs index 04b36440c..a40522564 100644 --- a/importer/markdown/.settings/org.eclipse.jdt.core.prefs +++ b/importer/markdown/.settings/org.eclipse.jdt.core.prefs @@ -1,6 +1,6 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning @@ -20,7 +20,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/importer/markdown/pom.xml b/importer/markdown/pom.xml index 4e6b034a0..afc2f622f 100644 --- a/importer/markdown/pom.xml +++ b/importer/markdown/pom.xml @@ -18,10 +18,14 @@ org.itsallcode.openfasttrace openfasttrace-api + + org.itsallcode.openfasttrace + openfasttrace-importer-lightweightmarkup + org.itsallcode.openfasttrace openfasttrace-testutil test - \ No newline at end of file + diff --git a/importer/markdown/src/main/java/module-info.java b/importer/markdown/src/main/java/module-info.java index a4c289ddc..e6e6430e8 100644 --- a/importer/markdown/src/main/java/module-info.java +++ b/importer/markdown/src/main/java/module-info.java @@ -1,13 +1,15 @@ +import org.itsallcode.openfasttrace.importer.markdown.MarkdownImporterFactory; + /** - * This provides an importer for the MarkDown format. + * This provides an importer for the Markdown format. * * @provides org.itsallcode.openfasttrace.api.importer.ImporterFactory */ module org.itsallcode.openfasttrace.importer.markdown { - requires java.logging; requires transitive org.itsallcode.openfasttrace.api; + requires org.itsallcode.openfasttrace.importer.lightweightmarkup; provides org.itsallcode.openfasttrace.api.importer.ImporterFactory - with org.itsallcode.openfasttrace.importer.markdown.MarkdownImporterFactory; + with MarkdownImporterFactory; } diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporter.java b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporter.java index 63a67d941..4014375bb 100644 --- a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporter.java +++ b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporter.java @@ -1,329 +1,169 @@ package org.itsallcode.openfasttrace.importer.markdown; -import static org.itsallcode.openfasttrace.importer.markdown.State.*; +import static org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.LineParserState.*; -import java.io.BufferedReader; -import java.io.IOException; -import java.util.logging.Logger; - -import org.itsallcode.openfasttrace.api.core.ItemStatus; -import org.itsallcode.openfasttrace.api.core.SpecificationItemId; -import org.itsallcode.openfasttrace.api.importer.*; +import org.itsallcode.openfasttrace.api.importer.ImportEventListener; import org.itsallcode.openfasttrace.api.importer.input.InputFile; - -class MarkdownImporter implements Importer +import org.itsallcode.openfasttrace.importer.lightweightmarkup.LightWeightMarkupImporter; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.*; + +/** + * Importer for OFT augmented Markdown. + *

+ * The purpose of this importer is to find specification items that follow a + * certain structure inside Markdown documents. It is not the goal to ingest the + * complete markdown document though, only the specification items. Also, the + * hierarchical structure of the document itself has no impact on the + * specification items. OFT just extracts a flat list. Linking the items is + * explicitly not the purpose of the importer. + *

+ */ +class MarkdownImporter extends LightWeightMarkupImporter { - private static final Logger LOG = Logger.getLogger(MarkdownImporter.class.getName()); - - // @formatter:off - private final Transition[] transitions = { - transition(START , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(START , TITLE , MdPattern.TITLE , this::rememberTitle ), - transition(START , OUTSIDE , MdPattern.FORWARD , this::forward ), - transition(START , OUTSIDE , MdPattern.EVERYTHING , () -> {} ), - - transition(TITLE , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(TITLE , TITLE , MdPattern.TITLE , this::rememberTitle ), - transition(TITLE , TITLE , MdPattern.EMPTY , () -> {} ), - transition(TITLE , OUTSIDE , MdPattern.EVERYTHING , this::resetTitle ), - - transition(OUTSIDE , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(OUTSIDE , OUTSIDE , MdPattern.FORWARD , this::forward ), - transition(OUTSIDE , TITLE , MdPattern.TITLE , this::rememberTitle ), - transition(OUTSIDE , TITLE , MdPattern.UNDERLINE , this::rememberPreviousLineAsTitle ), - - transition(SPEC_ITEM , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(SPEC_ITEM , SPEC_ITEM , MdPattern.STATUS , this::setStatus ), - transition(SPEC_ITEM , TITLE , MdPattern.TITLE , () -> {endItem(); rememberTitle();} ), - transition(SPEC_ITEM , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), - transition(SPEC_ITEM , COMMENT , MdPattern.COMMENT , this::beginComment ), - transition(SPEC_ITEM , COVERS , MdPattern.COVERS , () -> {} ), - transition(SPEC_ITEM , DEPENDS , MdPattern.DEPENDS , () -> {} ), - transition(SPEC_ITEM , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), - transition(SPEC_ITEM , NEEDS , MdPattern.NEEDS , () -> {} ), - transition(SPEC_ITEM , TAGS , MdPattern.TAGS_INT , this::addTag ), - transition(SPEC_ITEM , TAGS , MdPattern.TAGS , () -> {} ), - transition(SPEC_ITEM , DESCRIPTION, MdPattern.DESCRIPTION, this::beginDescription ), - transition(SPEC_ITEM , DESCRIPTION, MdPattern.NOT_EMPTY , this::beginDescription ), - - transition(DESCRIPTION, SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(DESCRIPTION, TITLE , MdPattern.TITLE , () -> {endItem(); rememberTitle();} ), - transition(DESCRIPTION, RATIONALE , MdPattern.RATIONALE , this::beginRationale ), - transition(DESCRIPTION, COMMENT , MdPattern.COMMENT , this::beginComment ), - transition(DESCRIPTION, COVERS , MdPattern.COVERS , () -> {} ), - transition(DESCRIPTION, DEPENDS , MdPattern.DEPENDS , () -> {} ), - transition(DESCRIPTION, NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), - transition(DESCRIPTION, NEEDS , MdPattern.NEEDS , () -> {} ), - transition(DESCRIPTION, TAGS , MdPattern.TAGS_INT , this::addTag ), - transition(DESCRIPTION, TAGS , MdPattern.TAGS , () -> {} ), - transition(DESCRIPTION, DESCRIPTION, MdPattern.EVERYTHING , this::appendDescription ), - - - transition(RATIONALE , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(RATIONALE , TITLE , MdPattern.TITLE , () -> {endItem(); rememberTitle();} ), - transition(RATIONALE , COMMENT , MdPattern.COMMENT , this::beginComment ), - transition(RATIONALE , COVERS , MdPattern.COVERS , () -> {} ), - transition(RATIONALE , DEPENDS , MdPattern.DEPENDS , () -> {} ), - transition(RATIONALE , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), - transition(RATIONALE , NEEDS , MdPattern.NEEDS , () -> {} ), - transition(RATIONALE , TAGS , MdPattern.TAGS_INT , this::addTag ), - transition(RATIONALE , TAGS , MdPattern.TAGS , () -> {} ), - transition(RATIONALE , RATIONALE , MdPattern.EVERYTHING , this::appendRationale ), - - transition(COMMENT , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(COMMENT , TITLE , MdPattern.TITLE , () -> {endItem(); rememberTitle();} ), - transition(COMMENT , COVERS , MdPattern.COVERS , () -> {} ), - transition(COMMENT , DEPENDS , MdPattern.DEPENDS , () -> {} ), - transition(COMMENT , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), - transition(COMMENT , NEEDS , MdPattern.NEEDS , () -> {} ), - transition(COMMENT , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), - transition(COMMENT , TAGS , MdPattern.TAGS_INT , this::addTag ), - transition(COMMENT , TAGS , MdPattern.TAGS , () -> {} ), - transition(COMMENT , COMMENT , MdPattern.EVERYTHING , this::appendComment ), - - - // [impl->dsn~md.covers-list~1] - transition(COVERS , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(COVERS , TITLE , MdPattern.TITLE , () -> {endItem(); rememberTitle();} ), - transition(COVERS , COVERS , MdPattern.COVERS_REF , this::addCoverage ), - transition(COVERS , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), - transition(COVERS , COMMENT , MdPattern.COMMENT , this::beginComment ), - transition(COVERS , DEPENDS , MdPattern.DEPENDS , () -> {} ), - transition(COVERS , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), - transition(COVERS , NEEDS , MdPattern.NEEDS , () -> {} ), - transition(COVERS , COVERS , MdPattern.EMPTY , () -> {} ), - transition(COVERS , TAGS , MdPattern.TAGS_INT , this::addTag ), - transition(COVERS , TAGS , MdPattern.TAGS , () -> {} ), - - // [impl->dsn~md.depends-list~1] - transition(DEPENDS , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(DEPENDS , TITLE , MdPattern.TITLE , () -> {endItem(); rememberTitle();} ), - transition(DEPENDS , DEPENDS , MdPattern.DEPENDS_REF, this::addDependency ), - transition(DEPENDS , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), - transition(DEPENDS , COMMENT , MdPattern.COMMENT , this::beginComment ), - transition(DEPENDS , DEPENDS , MdPattern.DEPENDS , () -> {} ), - transition(DEPENDS , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), - transition(DEPENDS , NEEDS , MdPattern.NEEDS , () -> {} ), - transition(DEPENDS , DEPENDS , MdPattern.EMPTY , () -> {} ), - transition(DEPENDS , COVERS , MdPattern.COVERS , () -> {} ), - transition(DEPENDS , TAGS , MdPattern.TAGS_INT , this::addTag ), - transition(DEPENDS , TAGS , MdPattern.TAGS , () -> {} ), - - // [impl->dsn~md.needs-coverage-list~2] - // [impl->dsn~md.needs-coverage-list-compact~1] - transition(NEEDS , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(NEEDS , TITLE , MdPattern.TITLE , () -> {endItem(); rememberTitle();} ), - transition(NEEDS , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), - transition(NEEDS , COMMENT , MdPattern.COMMENT , this::beginComment ), - transition(NEEDS , DEPENDS , MdPattern.DEPENDS , () -> {} ), - transition(NEEDS , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), - transition(NEEDS , NEEDS , MdPattern.NEEDS_REF , this::addNeeds ), - transition(NEEDS , NEEDS , MdPattern.EMPTY , () -> {} ), - transition(NEEDS , COVERS , MdPattern.COVERS , () -> {} ), - transition(NEEDS , TAGS , MdPattern.TAGS_INT , this::addTag ), - transition(NEEDS , TAGS , MdPattern.TAGS , () -> {} ), - - transition(TAGS , TAGS , MdPattern.TAG_ENTRY , this::addTag ), - transition(TAGS , SPEC_ITEM , MdPattern.ID , this::beginItem ), - transition(TAGS , TITLE , MdPattern.TITLE , () -> {endItem(); rememberTitle();} ), - transition(TAGS , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), - transition(TAGS , COMMENT , MdPattern.COMMENT , this::beginComment ), - transition(TAGS , DEPENDS , MdPattern.DEPENDS , () -> {} ), - transition(TAGS , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), - transition(TAGS , NEEDS , MdPattern.NEEDS , () -> {} ), - transition(TAGS , NEEDS , MdPattern.EMPTY , () -> {} ), - transition(TAGS , COVERS , MdPattern.COVERS , () -> {} ), - transition(TAGS , TAGS , MdPattern.TAGS , () -> {} ), - transition(TAGS , TAGS , MdPattern.TAGS_INT , this::addTag ) - }; - // @formatter:on - - private final InputFile file; - private final ImportEventListener listener; - private final MarkdownImporterStateMachine stateMachine; - private String lastTitle = null; - private String lastLine = null; - private boolean inSpecificationItem; - private int lineNumber = 0; - + private static final LinePattern SECTION_TITLE = new MdSectionTitlePattern(); + + /** + * Creates a {@link MarkdownImporter} object with the given parameters. + * + * @param fileName + * the input file to be imported + * @param listener + * the listener to handle import events + */ MarkdownImporter(final InputFile fileName, final ImportEventListener listener) { - this.file = fileName; - this.listener = listener; - this.stateMachine = new MarkdownImporterStateMachine(this.transitions); - } - - @Override - public void runImport() - { - LOG.fine(() -> "Starting import of file " + this.file); - String line; - this.lineNumber = 0; - try (BufferedReader reader = this.file.createReader()) - { - while ((line = reader.readLine()) != null) - { - ++this.lineNumber; - this.stateMachine.step(line); - this.lastLine = line; - } - } - catch (final IOException exception) - { - throw new ImporterException( - "Error reading \"" + this.file.getPath() + "\" at line " + this.lineNumber, - exception); - - } - finishImport(); - } - - private void finishImport() - { - if (this.inSpecificationItem) - { - this.listener.endSpecificationItem(); - } - } - - private static Transition transition(final State from, final State to, + super(fileName, listener); + } + + protected Transition[] configureTransitions() + { + // @formatter:off + return new Transition[]{ + transition(START , SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(START , TITLE , SECTION_TITLE , this::rememberTitle ), + transition(START , START , MdPattern.FORWARD , this::forward ), + transition(START , START , MdPattern.EVERYTHING , () -> {} ), + + transition(TITLE , SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(TITLE , TITLE , SECTION_TITLE , this::rememberTitle ), + transition(TITLE , TITLE , MdPattern.UNDERLINE , () -> {} ), + transition(TITLE , TITLE , MdPattern.EMPTY , () -> {} ), + transition(TITLE , START , MdPattern.FORWARD , () -> {forward(); resetTitle();} ), + transition(TITLE , START , MdPattern.EVERYTHING , this::resetTitle ), + + transition(SPEC_ITEM , SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(SPEC_ITEM , SPEC_ITEM , MdPattern.STATUS , this::setStatus ), + transition(SPEC_ITEM , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(SPEC_ITEM , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), + transition(SPEC_ITEM , COMMENT , MdPattern.COMMENT , this::beginComment ), + transition(SPEC_ITEM , COVERS , MdPattern.COVERS , () -> {} ), + transition(SPEC_ITEM , DEPENDS , MdPattern.DEPENDS , () -> {} ), + transition(SPEC_ITEM , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), + transition(SPEC_ITEM , NEEDS , MdPattern.NEEDS , () -> {} ), + transition(SPEC_ITEM , TAGS , MdPattern.TAGS_INT , this::addTag ), + transition(SPEC_ITEM , TAGS , MdPattern.TAGS , () -> {} ), + transition(SPEC_ITEM , DESCRIPTION, MdPattern.DESCRIPTION, this::beginDescription ), + transition(SPEC_ITEM , DESCRIPTION, MdPattern.NOT_EMPTY , this::beginDescription ), + + transition(DESCRIPTION, SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(DESCRIPTION, TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(DESCRIPTION, RATIONALE , MdPattern.RATIONALE , this::beginRationale ), + transition(DESCRIPTION, COMMENT , MdPattern.COMMENT , this::beginComment ), + transition(DESCRIPTION, COVERS , MdPattern.COVERS , () -> {} ), + transition(DESCRIPTION, DEPENDS , MdPattern.DEPENDS , () -> {} ), + transition(DESCRIPTION, NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), + transition(DESCRIPTION, NEEDS , MdPattern.NEEDS , () -> {} ), + transition(DESCRIPTION, TAGS , MdPattern.TAGS_INT , this::addTag ), + transition(DESCRIPTION, TAGS , MdPattern.TAGS , () -> {} ), + transition(DESCRIPTION, DESCRIPTION, MdPattern.EVERYTHING , this::appendDescription ), + + transition(RATIONALE , SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(RATIONALE , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(RATIONALE , COMMENT , MdPattern.COMMENT , this::beginComment ), + transition(RATIONALE , COVERS , MdPattern.COVERS , () -> {} ), + transition(RATIONALE , DEPENDS , MdPattern.DEPENDS , () -> {} ), + transition(RATIONALE , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), + transition(RATIONALE , NEEDS , MdPattern.NEEDS , () -> {} ), + transition(RATIONALE , TAGS , MdPattern.TAGS_INT , this::addTag ), + transition(RATIONALE , TAGS , MdPattern.TAGS , () -> {} ), + transition(RATIONALE , RATIONALE , MdPattern.EVERYTHING , this::appendRationale ), + + transition(COMMENT , SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(COMMENT , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(COMMENT , COVERS , MdPattern.COVERS , () -> {} ), + transition(COMMENT , DEPENDS , MdPattern.DEPENDS , () -> {} ), + transition(COMMENT , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), + transition(COMMENT , NEEDS , MdPattern.NEEDS , () -> {} ), + transition(COMMENT , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), + transition(COMMENT , TAGS , MdPattern.TAGS_INT , this::addTag ), + transition(COMMENT , TAGS , MdPattern.TAGS , () -> {} ), + transition(COMMENT , COMMENT , MdPattern.EVERYTHING , this::appendComment ), + + + // [impl->dsn~md.covers-list~1] + transition(COVERS , SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(COVERS , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(COVERS , COVERS , MdPattern.COVERS_REF , this::addCoverage ), + transition(COVERS , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), + transition(COVERS , COMMENT , MdPattern.COMMENT , this::beginComment ), + transition(COVERS , DEPENDS , MdPattern.DEPENDS , () -> {} ), + transition(COVERS , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), + transition(COVERS , NEEDS , MdPattern.NEEDS , () -> {} ), + transition(COVERS , COVERS , MdPattern.EMPTY , () -> {} ), + transition(COVERS , TAGS , MdPattern.TAGS_INT , this::addTag ), + transition(COVERS , TAGS , MdPattern.TAGS , () -> {} ), + transition(COVERS , START , MdPattern.FORWARD , () -> {endItem(); forward();} ), + + // [impl->dsn~md.depends-list~1] + transition(DEPENDS , SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(DEPENDS , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(DEPENDS , DEPENDS , MdPattern.DEPENDS_REF, this::addDependency ), + transition(DEPENDS , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), + transition(DEPENDS , COMMENT , MdPattern.COMMENT , this::beginComment ), + transition(DEPENDS , DEPENDS , MdPattern.DEPENDS , () -> {} ), + transition(DEPENDS , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), + transition(DEPENDS , NEEDS , MdPattern.NEEDS , () -> {} ), + transition(DEPENDS , DEPENDS , MdPattern.EMPTY , () -> {} ), + transition(DEPENDS , COVERS , MdPattern.COVERS , () -> {} ), + transition(DEPENDS , TAGS , MdPattern.TAGS_INT , this::addTag ), + transition(DEPENDS , TAGS , MdPattern.TAGS , () -> {} ), + transition(DEPENDS , START , MdPattern.FORWARD , () -> {endItem(); forward();} ), + + // [impl->dsn~md.needs-coverage-list-single-line~2] + // [impl->dsn~md.needs-coverage-list~1] + transition(NEEDS , SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(NEEDS , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(NEEDS , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), + transition(NEEDS , COMMENT , MdPattern.COMMENT , this::beginComment ), + transition(NEEDS , DEPENDS , MdPattern.DEPENDS , () -> {} ), + transition(NEEDS , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), + transition(NEEDS , NEEDS , MdPattern.NEEDS_REF , this::addNeeds ), + transition(NEEDS , NEEDS , MdPattern.EMPTY , () -> {} ), + transition(NEEDS , COVERS , MdPattern.COVERS , () -> {} ), + transition(NEEDS , TAGS , MdPattern.TAGS_INT , this::addTag ), + transition(NEEDS , TAGS , MdPattern.TAGS , () -> {} ), + transition(NEEDS , START , MdPattern.FORWARD , () -> {endItem(); forward();} ), + + transition(TAGS , TAGS , MdPattern.TAG_ENTRY , this::addTag ), + transition(TAGS , SPEC_ITEM , MdPattern.ID , this::beginItem ), + transition(TAGS , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(TAGS , RATIONALE , MdPattern.RATIONALE , this::beginRationale ), + transition(TAGS , COMMENT , MdPattern.COMMENT , this::beginComment ), + transition(TAGS , DEPENDS , MdPattern.DEPENDS , () -> {} ), + transition(TAGS , NEEDS , MdPattern.NEEDS_INT , this::addNeeds ), + transition(TAGS , NEEDS , MdPattern.NEEDS , () -> {} ), + transition(TAGS , NEEDS , MdPattern.EMPTY , () -> {} ), + transition(TAGS , COVERS , MdPattern.COVERS , () -> {} ), + transition(TAGS , TAGS , MdPattern.TAGS , () -> {} ), + transition(TAGS , TAGS , MdPattern.TAGS_INT , this::addTag ), + transition(TAGS , START , MdPattern.FORWARD , () -> {endItem(); forward();} ) + }; + // @formatter:on + } + + private static Transition transition(final LineParserState from, final LineParserState to, final MdPattern pattern, final TransitionAction action) { - return new Transition(from, to, pattern, action); - } - - private void beginItem() - { - cleanUpLastItem(); - this.inSpecificationItem = true; - informListenerAboutNewItem(); - } - - private void cleanUpLastItem() - { - if (this.inSpecificationItem) - { - endItem(); - } - } - - private void informListenerAboutNewItem() - { - final String idText = this.stateMachine.getLastToken(); - final SpecificationItemId id = new SpecificationItemId.Builder(idText).build(); - this.listener.beginSpecificationItem(); - this.listener.setId(id); - this.listener.setLocation(this.file.getPath(), this.lineNumber); - if (this.lastTitle != null) - { - this.listener.setTitle(this.lastTitle); - } - } - - private void endItem() - { - this.inSpecificationItem = false; - resetTitle(); - this.listener.endSpecificationItem(); - } - - private void setStatus() - { - this.listener.setStatus(ItemStatus.parseString(this.stateMachine.getLastToken())); - } - - private void beginDescription() - { - this.listener.appendDescription(this.stateMachine.getLastToken()); - } - - private void appendDescription() - { - this.listener.appendDescription(System.lineSeparator()); - this.listener.appendDescription(this.stateMachine.getLastToken()); - } - - private void beginRationale() - { - this.listener.appendRationale(System.lineSeparator()); - } - - private void appendRationale() - { - this.listener.appendRationale(System.lineSeparator()); - this.listener.appendRationale(this.stateMachine.getLastToken()); - } - - private void beginComment() - { - this.listener.appendComment(this.stateMachine.getLastToken()); - } - - private void appendComment() - { - this.listener.appendComment(System.lineSeparator()); - this.listener.appendComment(this.stateMachine.getLastToken()); - } - - private void addDependency() - { - final SpecificationItemId.Builder builder = new SpecificationItemId.Builder( - this.stateMachine.getLastToken()); - this.listener.addDependsOnId(builder.build()); - } - - private void addNeeds() - { - final String artifactTypes = this.stateMachine.getLastToken(); - for (final String artifactType : artifactTypes.split(",")) - { - this.listener.addNeededArtifactType(artifactType.trim()); - } - } - - private void rememberPreviousLineAsTitle() { - this.lastTitle = this.lastLine; - } - - // [impl->dsn~md.specification-item-title~1] - private void rememberTitle() - { - this.lastTitle = this.stateMachine.getLastToken(); - } - - - private void resetTitle() - { - this.lastTitle = null; - } - - private void addCoverage() - { - this.listener.addCoveredId(SpecificationItemId.parseId(this.stateMachine.getLastToken())); - } - - private void addTag() - { - final String tags = this.stateMachine.getLastToken(); - for (final String tag : tags.split(",")) - { - this.listener.addTag(tag.trim()); - } - } - - // [impl->dsn~md.artifact-forwarding-notation~1] - private void forward() - { - final MarkdownForwardingSpecificationItem forward = new MarkdownForwardingSpecificationItem( - this.stateMachine.getLastToken()); - this.listener.beginSpecificationItem(); - this.listener.setId(forward.getSkippedId()); - this.listener.addCoveredId(forward.getOriginalId()); - for (final String targetArtifactType : forward.getTargetArtifactTypes()) - { - this.listener.addNeededArtifactType(targetArtifactType.trim()); - } - this.listener.setForwards(true); - this.listener.endSpecificationItem(); + return new Transition(from, to, pattern.getPattern(), action); } } diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporterStateMachine.java b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporterStateMachine.java deleted file mode 100644 index 6ae40c30e..000000000 --- a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownImporterStateMachine.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.itsallcode.openfasttrace.importer.markdown; - -import java.util.logging.Logger; - -import java.util.regex.Matcher; - -/** - * This machine implements the core of a state based parser - * - * Before the state machine is run, it needs to be configured with a transition - * table in the constructor. - * - * Each step of the state machine gets a portion of the text to be imported as - * input. The machine checks the current state and the input on each step and - * decides on resulting state and action depending on the configuration provided - * in the transition table. - */ -public class MarkdownImporterStateMachine -{ - private static final Logger LOG = Logger - .getLogger(MarkdownImporterStateMachine.class.getName()); - - private State state = State.START; - private String lastToken = ""; - private final Transition[] transitions; - - /** - * Create a new instance of the {@link MarkdownImporterStateMachine} - * - * @param transitions - * the transition table that serves as configuration for the - * state machine - */ - public MarkdownImporterStateMachine(final Transition[] transitions) - { - this.transitions = transitions; - } - - /** - * Step the state machine - * - * @param line - * the text fragment on which the state machine decides the next - * state and action - */ - public void step(final String line) - { - for (final Transition entry : this.transitions) - { - if ((this.state == entry.getFrom()) && matchToken(line, entry)) - { - LOG.finest(() -> entry + " : '" + line + "'"); - entry.getTransition().transit(); - this.state = entry.getTo(); - break; - } - } - } - - private boolean matchToken(final String line, final Transition entry) - { - boolean matches = false; - final Matcher matcher = entry.getMarkdownPattern().getPattern().matcher(line); - if (matcher.matches()) - { - this.lastToken = (matcher.groupCount() == 0) ? "" : matcher.group(1); - matches = true; - } - return matches; - } - - /** - * Get the last text token that the state machine isolated - * - * @return the last text token - */ - public String getLastToken() - { - return this.lastToken; - } -} diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdPattern.java b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdPattern.java index f63aa8e19..b5cd0c4ce 100644 --- a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdPattern.java +++ b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdPattern.java @@ -1,8 +1,9 @@ package org.itsallcode.openfasttrace.importer.markdown; -import java.util.regex.Pattern; - import org.itsallcode.openfasttrace.api.core.SpecificationItemId; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.ForwardingSpecificationItem; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.LinePattern; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.SimpleLinePattern; /** * Patterns that describe tokens to be recognized within Markdown-style @@ -25,18 +26,18 @@ enum MdPattern FORWARD(".*?(" + PatternConstants.ARTIFACT_TYPE + "\\s*" - + MarkdownForwardingSpecificationItem.FORWARD_MARKER + + ForwardingSpecificationItem.FORWARD_MARKER + "\\s*" + PatternConstants.ARTIFACT_TYPE + "(?:,\\s*" + PatternConstants.ARTIFACT_TYPE + ")*" + "\\s*" - + MarkdownForwardingSpecificationItem.ORIGINAL_MARKER + + ForwardingSpecificationItem.ORIGINAL_MARKER + "\\s*" + SpecificationItemId.ID_PATTERN + ").*?"), - ID("`?((?:" + SpecificationItemId.ID_PATTERN + ")|(?:" + SpecificationItemId.LEGACY_ID_PATTERN + "))`?.*"), + ID("`?(" + SpecificationItemId.ID_PATTERN + ")`?.*"), NEEDS_INT("Needs:(\\s*\\w+\\s*(?:,\\s*\\w+\\s*)*)"), NEEDS("Needs:\\s*"), NEEDS_REF(PatternConstants.UP_TO_3_WHITESPACES + PatternConstants.BULLETS @@ -52,14 +53,14 @@ enum MdPattern + "\\s*" // + "(.*)"), TITLE("#+\\s*(.*)"), - UNDERLINE("([=-]{3,})"); + UNDERLINE("([=-]{3,})\\s*"); // @formatter:on - private final Pattern pattern; + private final LinePattern pattern; MdPattern(final String regularExpression) { - this.pattern = Pattern.compile(regularExpression); + this.pattern = SimpleLinePattern.of(regularExpression); } /** @@ -67,26 +68,25 @@ enum MdPattern * * @return the pattern */ - public Pattern getPattern() + public LinePattern getPattern() { return this.pattern; } - private static class PatternConstants + private static final class PatternConstants { - private PatternConstants() - { - // not instantiable - } - public static final String ARTIFACT_TYPE = "[a-zA-Z]+"; public static final String BULLETS = "[+*-]"; private static final String UP_TO_3_WHITESPACES = "\\s{0,3}"; // [impl->dsn~md.requirement-references~1] public static final String REFERENCE_AFTER_BULLET = UP_TO_3_WHITESPACES + PatternConstants.BULLETS + "(?:.*\\W)?" // - + "((?:" + SpecificationItemId.ID_PATTERN + ")|(?:" - + SpecificationItemId.LEGACY_ID_PATTERN + "))" // + + "(" + SpecificationItemId.ID_PATTERN + ")" // + "(?:\\W.*)?"; + + private PatternConstants() + { + // not instantiable + } } } diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdSectionTitlePattern.java b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdSectionTitlePattern.java new file mode 100644 index 000000000..6f915767c --- /dev/null +++ b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/MdSectionTitlePattern.java @@ -0,0 +1,31 @@ +package org.itsallcode.openfasttrace.importer.markdown; + +import java.util.List; +import java.util.Optional; + +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.LinePattern; + +class MdSectionTitlePattern implements LinePattern +{ + private static final LinePattern UNDERLINE = MdPattern.UNDERLINE.getPattern(); + private static final LinePattern HASH_TITLE = MdPattern.TITLE.getPattern(); + + @Override + public Optional> getMatches(final String line, final String nextLine) + { + if (line == null) + { + return Optional.empty(); + } + final Optional> hashTitle = HASH_TITLE.getMatches(line, null); + if (hashTitle.isPresent()) + { + return hashTitle; + } + if (nextLine != null && UNDERLINE.getMatches(nextLine, null).isPresent()) + { + return Optional.of(List.of(line)); + } + return Optional.empty(); + } +} diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/State.java b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/State.java deleted file mode 100644 index 83066cfed..000000000 --- a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/State.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.itsallcode.openfasttrace.importer.markdown; - -enum State -{ - START, OUTSIDE, SPEC_ITEM, DESCRIPTION, COVERS, DEPENDS, RATIONALE, COMMENT, NEEDS, EOF, TITLE, TAGS -} \ No newline at end of file diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/Transition.java b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/Transition.java deleted file mode 100644 index 343166771..000000000 --- a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/Transition.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.itsallcode.openfasttrace.importer.markdown; - -class Transition -{ - private final State from; - private final State to; - private final MdPattern markdownPattern; - private final TransitionAction transitionAction; - - public Transition(final State from, final State to, final MdPattern markdownPattern, - final TransitionAction transitionAction) - { - this.from = from; - this.to = to; - this.markdownPattern = markdownPattern; - this.transitionAction = transitionAction; - } - - public State getFrom() - { - return this.from; - } - - public State getTo() - { - return this.to; - } - - public MdPattern getMarkdownPattern() - { - return this.markdownPattern; - } - - public TransitionAction getTransition() - { - return this.transitionAction; - } - - @Override - public String toString() - { - return "Transition [from=" + this.from + ", to=" + this.to + ", markdownPattern=" - + this.markdownPattern + "]"; - } -} diff --git a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/TransitionAction.java b/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/TransitionAction.java deleted file mode 100644 index b1d801cf0..000000000 --- a/importer/markdown/src/main/java/org/itsallcode/openfasttrace/importer/markdown/TransitionAction.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.itsallcode.openfasttrace.importer.markdown; - -@FunctionalInterface -interface TransitionAction -{ - void transit(); -} diff --git a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/ITMarkdownImporter.java b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/ITMarkdownImporter.java deleted file mode 100644 index a9010c2cf..000000000 --- a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/ITMarkdownImporter.java +++ /dev/null @@ -1,311 +0,0 @@ -package org.itsallcode.openfasttrace.importer.markdown; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.equalTo; -import static org.itsallcode.openfasttrace.importer.markdown.MarkdownTestConstants.*; - -import java.io.BufferedReader; -import java.io.StringReader; -import java.nio.file.Paths; -import java.util.List; -import java.util.stream.Stream; - -import org.itsallcode.matcher.auto.AutoMatcher; -import org.itsallcode.openfasttrace.api.core.*; -import org.itsallcode.openfasttrace.api.importer.Importer; -import org.itsallcode.openfasttrace.api.importer.SpecificationListBuilder; -import org.itsallcode.openfasttrace.api.importer.input.InputFile; -import org.itsallcode.openfasttrace.testutil.importer.input.StreamInput; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.junit.jupiter.params.provider.ValueSource; - -class ITMarkdownImporter -{ - private static final String NL = System.lineSeparator(); - private static final String TAG2 = "Tag2"; - private static final String TAG1 = "Tag1"; - private static final String FILENAME = "file name"; - - @Test - void testFindRequirement() - { - assertThat(runImporterOnText(createCompleteSpecificationItemInMarkdownFormat()), - AutoMatcher.contains(SpecificationItem.builder().id(ID1).title("Requirement Title") - .comment("Comment" + NL + "More comment") - .description("Description" + NL + NL + "More description") - .rationale("Rationale" + NL + "More rationale") - .addNeedsArtifactType("artA").addNeedsArtifactType("artB") - .addCoveredId(SpecificationItemId.parseId(COVERED_ID1)) - .addCoveredId(SpecificationItemId.parseId(COVERED_ID2)) - .addDependOnId(SpecificationItemId.parseId(DEPENDS_ON_ID1)) - .addDependOnId(SpecificationItemId.parseId(DEPENDS_ON_ID2)) - .location("file name", 2) - .build())); - - } - - // [utest->dsn~md.needs-coverage-list-compact~1] - private String createCompleteSpecificationItemInMarkdownFormat() - { - return "# " + TITLE // - + "\n" // - + "`" + ID1 + "` " // - + "\n" // - + DESCRIPTION_LINE1 + "\n" // - + DESCRIPTION_LINE2 + "\n" // - + DESCRIPTION_LINE3 + "\n" // - + "\nRationale:\n" // - + RATIONALE_LINE1 + "\n" // - + RATIONALE_LINE2 + "\n" // - + "\nCovers:\n\n" // - + " * " + COVERED_ID1 + "\n" // - + " + " + "[Link to baz2](#" + COVERED_ID2 + ")\n" // - + "\nDepends:\n\n" // - + " + " + DEPENDS_ON_ID1 + "\n" // - + " - " + DEPENDS_ON_ID2 + "\n" // - + "\nComment:\n\n" // - + COMMENT_LINE1 + "\n" // - + COMMENT_LINE2 + "\n" // - + "\nNeeds: " + NEEDS_ARTIFACT_TYPE1 // - + " , " + NEEDS_ARTIFACT_TYPE2 + " "; - } - - private List runImporterOnText(final String text) - { - final BufferedReader reader = new BufferedReader(new StringReader(text)); - final InputFile file = StreamInput.forReader(Paths.get(FILENAME), reader); - final SpecificationListBuilder specItemBuilder = SpecificationListBuilder.create(); - final Importer importer = new MarkdownImporterFactory().createImporter(file, - specItemBuilder); - importer.runImport(); - return specItemBuilder.build(); - } - - @Test - void testTwoConsecutiveSpecificationItems() - { - assertThat(runImporterOnText(createTwoConsecutiveItemsInMarkdownFormat()), - AutoMatcher - .contains(SpecificationItem.builder().id(ID1).title(TITLE).location("file name", 2).build(), - SpecificationItem.builder().id(ID2).title("").location("file name", 4).build())); - } - - private String createTwoConsecutiveItemsInMarkdownFormat() - { - return "# " + TITLE // - + "\n" // - + ID1 + "\n" // - + "\n" + ID2 + "\n" // - + "# Irrelevant Title"; - } - - @Test - void testSingleNeeds() - { - final String singleNeedsItem = "`foo~bar~1`\n\nNeeds: " + NEEDS_ARTIFACT_TYPE1; - final List items = runImporterOnText(singleNeedsItem); - assertThat(items.get(0).getNeedsArtifactTypes(), contains(NEEDS_ARTIFACT_TYPE1)); - } - - @Test - void testFindLegacyRequirement() - { - final String completeItem = createCompleteSpecificationItemInLegacyMarkdownFormat(); - assertThat(runImporterOnText(completeItem), - AutoMatcher.contains(SpecificationItem.builder().id(SpecificationItemId.parseId(LEGACY_ID)) - .title("Requirement Title") - .status(ItemStatus.PROPOSED) - .comment("Comment" + NL + "More comment") - .description("Description" + NL + NL + "More description") - .rationale("Rationale" + NL + "More rationale") - .addNeedsArtifactType("artA").addNeedsArtifactType("artB") - .addCoveredId(SpecificationItemId.parseId(LEGACY_COVERED_ID1)) - .addCoveredId(SpecificationItemId.parseId(LEGACY_COVERED_ID2)) - .addDependOnId(SpecificationItemId.parseId(LEGACY_DEPENDS_ON_ID1)) - .addDependOnId(SpecificationItemId.parseId(LEGACY_DEPENDS_ON_ID2)) - .addTag("Tag1").addTag("Tag2") - .location("file name", 2) - .build())); - } - - // [utest->dsn~md.needs-coverage-list~2] - private String createCompleteSpecificationItemInLegacyMarkdownFormat() - { - return "# " + TITLE // - + "\n" // - + "`" + LEGACY_ID + "`" // - + "\n" // - + "\nStatus: proposed\n" // - + "\nDescription:\n" + DESCRIPTION_LINE1 + "\n" // - + DESCRIPTION_LINE2 + "\n" // - + DESCRIPTION_LINE3 + "\n" // - + "\nRationale:\n" // - + RATIONALE_LINE1 + "\n" // - + RATIONALE_LINE2 + "\n" // - + "\nDepends:\n\n" // - + " + `" + LEGACY_DEPENDS_ON_ID1 + "`\n" // - + " - `" + LEGACY_DEPENDS_ON_ID2 + "`\n" // - + "\nCovers:\n\n" // - + " * `" + LEGACY_COVERED_ID1 + "`\n" // - + " + `" + LEGACY_COVERED_ID2 + "`\n" // - + "\nComment:\n\n" // - + COMMENT_LINE1 + "\n" // - + COMMENT_LINE2 + "\n" // - + "\nNeeds:\n" // - + " * " + NEEDS_ARTIFACT_TYPE1 + "\n"// - + "+ " + NEEDS_ARTIFACT_TYPE2 + "\n" // - + "\nTags: " + TAG1 + ", " + TAG2; - } - - // [utest->dsn~md.artifact-forwarding-notation~1] - @Test - void testForwardRequirement() - { - final List items = runImporterOnText("arch-->dsn:req~foobar~2\n" // - + " * `dsn --> impl, utest,itest : arch~bar.zoo~123`"); - assertThat(items, - AutoMatcher.contains(SpecificationItem.builder().id(SpecificationItemId.parseId("arch~foobar~2")) - .forwards(true) - .addCoveredId(SpecificationItemId.parseId("req~foobar~2")) - .addNeedsArtifactType("dsn") - .build(), - SpecificationItem.builder().id(SpecificationItemId.parseId("dsn~bar.zoo~123")) - .addCoveredId(SpecificationItemId.parseId("arch~bar.zoo~123")) - .addNeedsArtifactType("impl").addNeedsArtifactType("utest") - .addNeedsArtifactType("itest") - .forwards(true) - .build())); - } - - // [utest->dsn~md.specification-item-title~1] - @Test - void testFindTitleAfterTitle() - { - assertThat(runImporterOnText("## This title should be ignored\n\n" // - + "### Title\n" // - + "`a~b~1`"), - AutoMatcher.contains(SpecificationItem.builder().id(SpecificationItemId.parseId("a~b~1")) - .title("Title").location("file name", 4) - .build())); - } - - @ParameterizedTest - @MethodSource("needsCoverage") - void testNeedsCoverage(final String mdContent, final List expected) - { - final List items = runImporterOnText("`a~b~1`\n" + mdContent); - assertThat(items.get(0).getNeedsArtifactTypes(), equalTo(expected)); - } - - static Stream needsCoverage() - { - return Stream.of( - Arguments.of("Needs: req , dsn ", List.of("req", "dsn")), - Arguments.of("Needs: req ", List.of("req")), - Arguments.of("Needs: req,dsn ", List.of("req", "dsn")), - Arguments.of("Needs: req ,dsn", List.of("req", "dsn")), - Arguments.of("Needs: req,dsn", List.of("req", "dsn")), - Arguments.of("Needs: req,\tdsn\n", List.of("req", "dsn")), - Arguments.of("Needs:req,dsn", List.of("req", "dsn")), - Arguments.of("Needs:\n* req\n* dsn", List.of("req", "dsn")), - Arguments.of("Needs:\n * req\n * dsn", List.of("req", "dsn")), - Arguments.of("Needs:\n* req \n\t* dsn ", List.of("req", "dsn")), - Arguments.of("Needs:\n* req\n* dsn", List.of("req", "dsn"))); - } - - @ParameterizedTest - @MethodSource("tags") - void testTags(final String mdContent, final List expected) - { - final List items = runImporterOnText("`a~b~1`\n" + mdContent); - assertThat(items.get(0).getTags(), equalTo(expected)); - } - - static Stream tags() - { - return Stream.of( - Arguments.of("Tags: req , dsn ", List.of("req", "dsn")), - Arguments.of("Tags: req ", List.of("req")), - Arguments.of("Tags: req,dsn ", List.of("req", "dsn")), - Arguments.of("Tags: req ,dsn", List.of("req", "dsn")), - Arguments.of("Tags: req,dsn", List.of("req", "dsn")), - Arguments.of("Tags: req,\tdsn\n", List.of("req", "dsn")), - Arguments.of("Tags:req,dsn", List.of("req", "dsn")), - Arguments.of("Tags:\n* req\n* dsn", List.of("req", "dsn")), - Arguments.of("Tags:\n * req\n * dsn\n", List.of("req", "dsn")), - Arguments.of("Tags:\n* req \n\t* dsn ", List.of("req", "dsn")), - Arguments.of("Tags:\n* req\n* dsn", List.of("req", "dsn"))); - } - - @Test - void testItemIdSupportsUmlauts() - { - final List items = runImporterOnText( - "`### Die Implementierung muss den Zustand einzelner Zellen ändern.\n" - + "`req~zellzustandsänderung~1`\n" - + "Diese Anforderung ermöglicht die Aktualisierung des Zustands von lebenden und toten Zellen" - + " in jeder Generation.\n" - + "Needs: arch"); - assertThat(items, AutoMatcher.contains(SpecificationItem.builder() - .id(SpecificationItemId.createId("req", "zellzustandsänderung", 1)) - .description( - "Diese Anforderung ermöglicht die Aktualisierung des Zustands von lebenden und toten Zellen" - + " in jeder Generation.") - .location("file name", 2).addNeedsArtifactType("arch") - .build())); - } - - @ParameterizedTest - @ValueSource(strings = - { "---------------------------------", "---", "===", "======" }) - void testRecognizeItemTitleWithUnderlines(final String underline) - { - final List items = runImporterOnText( - "This is a title with an underline\n" // - + underline + "\n" // - + "`extra~support-underlined-headers~1`\n" // - + "Body text.\n"); - assertThat(items, AutoMatcher.contains(SpecificationItem.builder() - .id(SpecificationItemId.createId("extra", "support-underlined-headers", 1)) - .title("This is a title with an underline") - .description("Body text.") - .location("file name", 3) - .build())); - } - - @ParameterizedTest - @ValueSource(strings = - { "---------------------------------", "---", "===", "======", "================================================" }) - void testRecognizeItemTitleWithUnderlinesAfterAnotherTitle(final String underline) - { - final List items = runImporterOnText( - "# This must be ignored.\n" // - + "This is a title with an underline\n" // - + underline + "\n" // - + "`extra~support-underlined-headers~1`\n" // - + "Body text.\n"); - assertThat(items, AutoMatcher.contains(SpecificationItem.builder() - .id(SpecificationItemId.createId("extra", "support-underlined-headers", 1)) - .title("This is a title with an underline") - .description("Body text.") - .location("file name", 4) - .build())); - } - - @Test - void testLessThenTwoUnderliningCharactersAreNotDetectedAsTitleUnderlines() - { - final List items = runImporterOnText( - "This is not a title since the underline is too short\n" - + "--\n" - + "req~too-short~111"); - assertThat(items, AutoMatcher.contains(SpecificationItem.builder() - .id(SpecificationItemId.createId("req", "too-short", 111)) - .location("file name", 3) - .build())); - } -} diff --git a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownAsserts.java b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownAsserts.java index 43cbf74da..2c8815396 100644 --- a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownAsserts.java +++ b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownAsserts.java @@ -1,10 +1,10 @@ package org.itsallcode.openfasttrace.importer.markdown; import static org.hamcrest.MatcherAssert.assertThat; - import static org.hamcrest.Matchers.equalTo; -import java.util.regex.Matcher; +import java.util.List; +import java.util.Optional; class MarkdownAsserts { @@ -23,9 +23,9 @@ static void assertMatching(final String[] samples, final MdPattern mdPattern, { for (final String text : samples) { - final Matcher matcher = mdPattern.getPattern().matcher(text); + final Optional> matcher = mdPattern.getPattern().getMatches(text, null); assertThat(mdPattern.toString() + " must " + (mustMatch ? "" : "not ") + "match " + "\"" - + text + "\"", matcher.matches(), equalTo(mustMatch)); + + text + "\"", matcher.isPresent(), equalTo(mustMatch)); } } } diff --git a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownTestConstants.java b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownTestConstants.java index 63f5ac330..8a24c124f 100644 --- a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownTestConstants.java +++ b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MarkdownTestConstants.java @@ -9,22 +9,7 @@ private MarkdownTestConstants() // not instantiable } - static final SpecificationItemId ID1 = SpecificationItemId.parseId("type~id~1"); static final SpecificationItemId ID2 = SpecificationItemId.parseId("type~id~2"); - static final String TITLE = "Requirement Title"; - static final String DESCRIPTION_LINE1 = "Description"; - static final String DESCRIPTION_LINE2 = ""; - static final String DESCRIPTION_LINE3 = "More description"; - static final String RATIONALE_LINE1 = "Rationale"; - static final String RATIONALE_LINE2 = "More rationale"; - static final String COMMENT_LINE1 = "Comment"; - static final String COMMENT_LINE2 = "More comment"; - static final String COVERED_ID1 = "impl~foo1~1"; - static final String COVERED_ID2 = "impl~baz2~2"; - static final String NEEDS_ARTIFACT_TYPE1 = "artA"; - static final String NEEDS_ARTIFACT_TYPE2 = "artB"; - static final String DEPENDS_ON_ID1 = "configuration~blubb.blah.blah~4711"; - static final String DEPENDS_ON_ID2 = "db~blah.blubb~42"; // Legacy Markdown format static final String LEGACY_ID = "type~type:legacy_id, v3"; static final String LEGACY_COVERED_ID1 = "impl:legacy_foo1, v3"; diff --git a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MdSectionTitlePatternTest.java b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MdSectionTitlePatternTest.java new file mode 100644 index 000000000..33c73e67d --- /dev/null +++ b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/MdSectionTitlePatternTest.java @@ -0,0 +1,81 @@ +package org.itsallcode.openfasttrace.importer.markdown; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class MdSectionTitlePatternTest +{ + static Stream testCases() + { + return Stream.of( + titleNotRecongnized(null, null), + titleNotRecongnized(null, "ignored"), + titleNotRecongnized(null, "===="), + titleNotRecongnized("ignored", null), + testCase("# Title", null, "Title"), + testCase("## Title", null, "Title"), + testCase("## Title with words", null, "Title with words"), + testCase("## \tLeading whitespace removed ", null, "Leading whitespace removed "), + testCase("# Title", "ignored", "Title"), + testCase("# Title", "=======", "Title"), + testCase("Title with words", "=======", "Title with words"), + testCase("\t Leading & trailing whitespace not removed ", "=======", + "\t Leading & trailing whitespace not removed "), + underlineRecognized("======="), + underlineRecognized("-------"), + underlineRecognized("==="), + underlineRecognized("---"), + underlineNotRecognized("=="), + underlineNotRecognized("--"), + underlineNotRecognized("______"), + underlineNotRecognized("^^^^^^")); + } + + private static Arguments underlineRecognized(final String underline) + { + return Arguments.of("Title", underline, "Title"); + } + + private static Arguments underlineNotRecognized(final String underline) + { + return Arguments.of("Title", underline, null); + } + + private static Arguments titleNotRecongnized(final String line, final String nextLine) + { + return testCase(line, nextLine, null); + } + + private static Arguments testCase(final String line, final String nextLine, final String expected) + { + return Arguments.of(line, nextLine, expected); + } + + @ParameterizedTest + @MethodSource("testCases") + void test(final String line, final String nextLine, final String expected) + { + final MdSectionTitlePattern pattern = new MdSectionTitlePattern(); + final Optional> result = pattern.getMatches(line, nextLine); + if (expected == null) + { + assertThat("Lines '" + line + "' + '" + nextLine + "' should not be recognized as a section title", + result.isPresent(), is(false)); + } + else + { + assertAll(() -> assertThat( + "Lines '" + line + "' + '" + nextLine + "' should be recognized as a section title", + result.isPresent(), is(true)), () -> assertThat(result.get().get(0), is(expected))); + } + } +} diff --git a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownImporter.java b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownImporter.java index fdd3a2af4..8f8478927 100644 --- a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownImporter.java +++ b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownImporter.java @@ -2,37 +2,25 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; -import static org.itsallcode.openfasttrace.importer.markdown.MarkdownAsserts.assertMatch; -import static org.itsallcode.openfasttrace.importer.markdown.MarkdownAsserts.assertMismatch; -import static org.itsallcode.openfasttrace.importer.markdown.MarkdownTestConstants.*; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.when; -import java.io.*; -import java.nio.file.Paths; +import java.io.BufferedReader; +import java.io.IOException; -import org.itsallcode.openfasttrace.api.core.SpecificationItemId; import org.itsallcode.openfasttrace.api.importer.ImportEventListener; -import org.itsallcode.openfasttrace.api.importer.Importer; import org.itsallcode.openfasttrace.api.importer.ImporterException; import org.itsallcode.openfasttrace.api.importer.input.InputFile; -import org.itsallcode.openfasttrace.testutil.importer.input.StreamInput; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class TestMarkdownImporter { - private static final String FILENAME = "file name"; - - @Mock - ImportEventListener listenerMock; - // [utest->dsn~md.specification-item-id-format~3] @ParameterizedTest @CsvSource( @@ -44,7 +32,7 @@ class TestMarkdownImporter }) void testIdentifyId(final String text) { - assertMatch(MdPattern.ID, text); + MarkdownAsserts.assertMatch(MdPattern.ID, text); } // [utest->dsn~md.specification-item-id-format~3] @@ -53,7 +41,7 @@ void testIdentifyId(final String text) { "test~1", "req-test~1", "req~4test~1", "räq~test~1" }) void testIdentifyNonId(final String text) { - assertMismatch(MdPattern.ID, text); + MarkdownAsserts.assertMismatch(MdPattern.ID, text); } // [utest->dsn~md.specification-item-title~1] @@ -62,7 +50,7 @@ void testIdentifyNonId(final String text) { "#Title", "# Title", "###### Title", "# Title", "# Änderung" }) void testIdentifyTitle(final String text) { - assertMatch(MdPattern.TITLE, text); + MarkdownAsserts.assertMatch(MdPattern.TITLE, text); } // [utest->dsn~md.specification-item-title~1] @@ -71,7 +59,7 @@ void testIdentifyTitle(final String text) { "Title", "Title #", "' # Title'" }) void testIdentifyNonTitle(final String text) { - assertMismatch(MdPattern.TITLE, text); + MarkdownAsserts.assertMismatch(MdPattern.TITLE, text); } @ParameterizedTest @@ -79,7 +67,7 @@ void testIdentifyNonTitle(final String text) { "Needs: req, dsn", "Needs:req,dsn", "'Needs: \treq , dsn '" }) void testIdentifyNeeds(final String text) { - assertMatch(MdPattern.NEEDS_INT, text); + MarkdownAsserts.assertMatch(MdPattern.NEEDS_INT, text); } @ParameterizedTest @@ -87,7 +75,7 @@ void testIdentifyNeeds(final String text) { "Needs:", "#Needs: abc", "' Needs: abc'", "Needs: önderung" }) void testIdentifyNonNeeds(final String text) { - assertMismatch(MdPattern.NEEDS_INT, text); + MarkdownAsserts.assertMismatch(MdPattern.NEEDS_INT, text); } @ParameterizedTest @@ -95,7 +83,7 @@ void testIdentifyNonNeeds(final String text) { "Tags: req, dsn", "Tags:req,dsn", "'Tags: \treq , dsn '" }) void testIdentifyTags(final String text) { - assertMatch(MdPattern.TAGS_INT, text); + MarkdownAsserts.assertMatch(MdPattern.TAGS_INT, text); } @ParameterizedTest @@ -103,140 +91,18 @@ void testIdentifyTags(final String text) { "Tags:", "#Needs: abc", "' Needs: abc'", "Needs: änderung" }) void testIdentifyNonTags(final String text) { - assertMismatch(MdPattern.TAGS_INT, text); - } - - @Test - void testFindRequirement() - { - final String completeItem = createCompleteSpecificationItemInMarkdownFormat(); - runImporterOnText(completeItem); - assertAllImporterEventsCalled(); - } - - // [utest->dsn~md.needs-coverage-list-compact~1] - private String createCompleteSpecificationItemInMarkdownFormat() - { - return "# " + TITLE // - + "\n" // - + "`" + ID1 + "` " // - + "\n" // - + DESCRIPTION_LINE1 + "\n" // - + DESCRIPTION_LINE2 + "\n" // - + DESCRIPTION_LINE3 + "\n" // - + "\nRationale:\n" // - + RATIONALE_LINE1 + "\n" // - + RATIONALE_LINE2 + "\n" // - + "\nCovers:\n\n" // - + " * " + COVERED_ID1 + "\n" // - + " + " + "[Link to baz2](#" + COVERED_ID2 + ")\n" // - + "\nDepends:\n\n" // - + " + " + DEPENDS_ON_ID1 + "\n" // - + " - " + DEPENDS_ON_ID2 + "\n" // - + "\nComment:\n\n" // - + COMMENT_LINE1 + "\n" // - + COMMENT_LINE2 + "\n" // - + "\nNeeds: " + NEEDS_ARTIFACT_TYPE1 // - + " , " + NEEDS_ARTIFACT_TYPE2 + " "; - } - - private void runImporterOnText(final String text) - { - final BufferedReader reader = new BufferedReader(new StringReader(text)); - final InputFile file = StreamInput.forReader(Paths.get(FILENAME), reader); - final Importer importer = new MarkdownImporterFactory().createImporter(file, - this.listenerMock); - importer.runImport(); - } - - // [utest->dsn~md.covers-list~1] - // [utest->dsn~md.depends-list~1] - // [utest->dsn~md.requirement-references~1] - private void assertAllImporterEventsCalled() - { - final InOrder inOrder = inOrder(this.listenerMock); - inOrder.verify(this.listenerMock).beginSpecificationItem(); - inOrder.verify(this.listenerMock).setId(ID1); - inOrder.verify(this.listenerMock).setLocation(FILENAME, 2); - inOrder.verify(this.listenerMock).setTitle(TITLE); - inOrder.verify(this.listenerMock).appendDescription(DESCRIPTION_LINE1); - inOrder.verify(this.listenerMock).appendDescription(System.lineSeparator()); - inOrder.verify(this.listenerMock).appendDescription(DESCRIPTION_LINE2); - inOrder.verify(this.listenerMock).appendDescription(System.lineSeparator()); - inOrder.verify(this.listenerMock).appendDescription(DESCRIPTION_LINE3); - inOrder.verify(this.listenerMock).appendRationale(RATIONALE_LINE1); - inOrder.verify(this.listenerMock).appendRationale(RATIONALE_LINE2); - inOrder.verify(this.listenerMock).addCoveredId(SpecificationItemId.parseId(COVERED_ID1)); - inOrder.verify(this.listenerMock).addCoveredId(SpecificationItemId.parseId(COVERED_ID2)); - inOrder.verify(this.listenerMock) - .addDependsOnId(SpecificationItemId.parseId(MarkdownTestConstants.DEPENDS_ON_ID1)); - inOrder.verify(this.listenerMock) - .addDependsOnId(SpecificationItemId.parseId(DEPENDS_ON_ID2)); - inOrder.verify(this.listenerMock).appendComment(COMMENT_LINE1); - inOrder.verify(this.listenerMock).appendComment(COMMENT_LINE2); - inOrder.verify(this.listenerMock).addNeededArtifactType(NEEDS_ARTIFACT_TYPE1); - inOrder.verify(this.listenerMock).addNeededArtifactType(NEEDS_ARTIFACT_TYPE2); - inOrder.verify(this.listenerMock).endSpecificationItem(); - inOrder.verifyNoMoreInteractions(); - } - - @Test - void testTwoConsecutiveSpecificationItems() - { - runImporterOnText(createTwoConsecutiveItemsInMarkdownFormat()); - assertImporterEventsForTwoConsecutiveItemsCalled(); - } - - private String createTwoConsecutiveItemsInMarkdownFormat() - { - return "# " + TITLE // - + "\n" // - + ID1 + "\n" // - + "\n" + ID2 + "\n" // - + "# Irrelevant Title"; - } - - private void assertImporterEventsForTwoConsecutiveItemsCalled() - { - final InOrder inOrder = inOrder(this.listenerMock); - inOrder.verify(this.listenerMock).beginSpecificationItem(); - inOrder.verify(this.listenerMock).setId(ID1); - inOrder.verify(this.listenerMock).setLocation(FILENAME, 2); - inOrder.verify(this.listenerMock).setTitle(TITLE); - inOrder.verify(this.listenerMock).endSpecificationItem(); - inOrder.verify(this.listenerMock).beginSpecificationItem(); - inOrder.verify(this.listenerMock).setId(ID2); - inOrder.verify(this.listenerMock).setLocation(FILENAME, 4); - inOrder.verify(this.listenerMock).endSpecificationItem(); - inOrder.verifyNoMoreInteractions(); - } - - @Test - void testSingleNeeds() - { - final String singleNeedsItem = "`foo~bar~1`\n\nNeeds: " + NEEDS_ARTIFACT_TYPE1; - runImporterOnText(singleNeedsItem); - verify(this.listenerMock, times(1)).addNeededArtifactType(NEEDS_ARTIFACT_TYPE1); - } - - // [utest->dsn~md.eb-markdown-id~1] - @Test - void testIdentifyLegacyId() - { - assertMatch(MdPattern.ID, "a:b, v0", "req:test, v1", "req:test,v1", "req:test, v999", - "req:test.requirement, v1", "req:test_underscore, v1", - "`req:test1, v1`arbitrary text"); - assertMismatch(MdPattern.ID, "test, v1", "req-test, v1", "req.4test, v1"); + MarkdownAsserts.assertMismatch(MdPattern.TAGS_INT, text); } @Test void testRunImportHandlesIOException(@Mock final InputFile fileMock, @Mock final ImportEventListener listenerMock, - @Mock final BufferedReader readerMock) throws IOException { + @Mock final BufferedReader readerMock) throws IOException + { when(fileMock.getPath()).thenReturn("/the/file"); when(fileMock.createReader()).thenReturn(readerMock); when(readerMock.readLine()).thenThrow(new IOException("Dummy exception")); final MarkdownImporter importer = new MarkdownImporter(fileMock, listenerMock); final ImporterException exception = assertThrows(ImporterException.class, importer::runImport); - assertThat(exception.getMessage(), equalTo("Error reading \"/the/file\" at line 0")); + assertThat(exception.getMessage(), equalTo("Error reading '/the/file' at line 0: Dummy exception")); } } diff --git a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownMarkupImporter.java b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownMarkupImporter.java new file mode 100644 index 000000000..2f6595cc1 --- /dev/null +++ b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TestMarkdownMarkupImporter.java @@ -0,0 +1,141 @@ +package org.itsallcode.openfasttrace.importer.markdown; + +import static org.itsallcode.matcher.auto.AutoMatcher.contains; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.item; + +import org.itsallcode.openfasttrace.api.core.SpecificationItemId; +import org.itsallcode.openfasttrace.api.importer.ImporterFactory; +import org.itsallcode.openfasttrace.testutil.importer.lightweightmarkup.AbstractLightWeightMarkupImporterTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class TestMarkdownMarkupImporter extends AbstractLightWeightMarkupImporterTest +{ + private final static ImporterFactory importerFactory = new MarkdownImporterFactory(); + + TestMarkdownMarkupImporter() + { + super(0); + } + + @Override + protected ImporterFactory getImporterFactory() + { + return importerFactory; + } + + protected String formatTitle(final String title, final int level) + { + return "#".repeat(level) + " " + title; + } + + // [utest -> dsn~md.specification-item-title~1] + @Test + void testMarkdownTitleBeforeRequirementIdIsRequirementTitle() + { + assertImport("titles.md", + """ + # The title + the~id~1 + """, + contains(item() + .id("the", "id", 1) + .title("The title") + .location("titles.md", 2) + .build())); + } + + // [utest -> dsn~md.specification-item-title~1] + @Test + void testMarkdownTitleDetectedAfterAnotherTitle() + { + assertImport("more_titles.md", + """ + # 1st level title + + # 2nd level title + + the~id~1 + """, + contains(item() + .title("2nd level title") + .id("the", "id", 1) + .location("more_titles.md", 5) + .build())); + } + + // [utest->dsn~md.specification-item-title~1] + @Test + void testFindTitleAfterTitle() + { + assertImport("x", """ + ## This title should be ignored + + ### Title + `a~b~1 + """, + contains(item() + .id(SpecificationItemId.parseId("a~b~1")) + .title("Title").location("x", 4) + .build())); + } + + @ParameterizedTest + @ValueSource(strings = + { "---------------------------------", "---", "===", "======", "--- ", + "=== ", "---\t" }) + void testRecognizeItemTitleWithUnderlines(final String underline) + { + assertImport("file name", """ + This is a title with an underline + %s + `extra~support-underlined-headers~1` + Body text. + """.formatted(underline), + contains(item() + .id(SpecificationItemId.createId("extra", "support-underlined-headers", + 1)) + .title("This is a title with an underline") + .description("Body text.") + .location("file name", 3) + .build())); + } + + @ValueSource(strings = { "---------------------------------", "---", "===", "======", + "================================================", + "--- ", "=== ", "---\t" + }) + @ParameterizedTest + void testRecognizeItemTitleWithUnderlinesAfterAnotherTitle(final String underline) + { + assertImport("y", """ + # This must be ignored. + This is a title with an underline + %s + `extra~support-underlined-headers~1` + Body text. + """.formatted(underline), + contains(item() + .id(SpecificationItemId.createId("extra", "support-underlined-headers", + 1)) + .title("This is a title with an underline") + .description("Body text.") + .location("y", 4) + .build())); + } + + @Test + void testLessThenThreeUnderliningCharactersAreNotDetectedAsTitleUnderlines() + { + assertImport("z", """ + This is not a title since the underline is too short + -- + req~too-short~111 + """, + contains(item() + .id(SpecificationItemId.createId("req", "too-short", 111)) + .location("z", 3) + .build())); + } +} diff --git a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TransitionTest.java b/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TransitionTest.java deleted file mode 100644 index 20dfd8af4..000000000 --- a/importer/markdown/src/test/java/org/itsallcode/openfasttrace/importer/markdown/TransitionTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.itsallcode.openfasttrace.importer.markdown; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; - -@ExtendWith(MockitoExtension.class) -class TransitionTest -{ - @Test - void testToString(@Mock final TransitionAction actionMock) - { - final Transition transition = new Transition(State.OUTSIDE, State.TITLE, MdPattern.TITLE, actionMock); - assertThat(transition.toString(), equalTo("Transition [from=OUTSIDE, to=TITLE, markdownPattern=TITLE]")); - } -} \ No newline at end of file diff --git a/importer/markdown/src/test/resources/logging.properties b/importer/markdown/src/test/resources/logging.properties new file mode 100644 index 000000000..b2c9cbc7e --- /dev/null +++ b/importer/markdown/src/test/resources/logging.properties @@ -0,0 +1,11 @@ +handlers = java.util.logging.ConsoleHandler org.itsallcode.openfasttrace.testutil.log.NoOpLoggingHandler +.level = INFO +java.util.logging.ConsoleHandler.level = ALL +java.util.logging.ConsoleHandler.formatter = org.itsallcode.openfasttrace.testutil.log.ShortClassNameFormatter +java.util.logging.ConsoleHandler.encoding = UTF-8 + +org.itsallcode.openfasttrace.testutil.log.NoOpLoggingHandler.level = ALL + +# Set this to FINEST for debugging the state machine +org.itsallcode.openfasttrace.testutil.level = FINE +org.itsallcode.openfasttrace.importer.level = FINE diff --git a/importer/restructuredtext/.settings/org.eclipse.jdt.core.prefs b/importer/restructuredtext/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..a40522564 --- /dev/null +++ b/importer/restructuredtext/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,413 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.doc.comment.support=enabled +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning +org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled +org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public +org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected +org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags +org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled +org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=17 +org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false +org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false +org.eclipse.jdt.core.formatter.align_with_spaces=false +org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assertion_message=0 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 +org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_record_components=16 +org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_type_annotations=0 +org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=next_line_on_wrap +org.eclipse.jdt.core.formatter.brace_position_for_block=next_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=next_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=next_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=next_line +org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=next_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line +org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false +org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.indent_tag_description=false +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=false +org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=true +org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false +org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=space +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.text_block_indentation=0 +org.eclipse.jdt.core.formatter.use_on_off_tags=true +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=true +org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true +org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true +org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true +org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true +org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true +org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/importer/restructuredtext/.settings/org.eclipse.jdt.ui.prefs b/importer/restructuredtext/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..0a69ece30 --- /dev/null +++ b/importer/restructuredtext/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,146 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_itsallcode style +formatter_settings_version=21 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.ondemandthreshold=4 +org.eclipse.jdt.ui.staticondemandthreshold=4 +sp_cleanup.add_all=false +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.array_with_curly=false +sp_cleanup.arrays_fill=false +sp_cleanup.bitwise_conditional_expression=false +sp_cleanup.boolean_literal=false +sp_cleanup.boolean_value_rather_than_comparison=false +sp_cleanup.break_loop=false +sp_cleanup.collection_cloning=false +sp_cleanup.comparing_on_criteria=false +sp_cleanup.comparison_statement=false +sp_cleanup.controlflow_merge=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false +sp_cleanup.convert_to_switch_expressions=false +sp_cleanup.correct_indentation=false +sp_cleanup.do_while_rather_than_while=false +sp_cleanup.double_negation=false +sp_cleanup.else_if=false +sp_cleanup.embedded_if=false +sp_cleanup.evaluate_nullable=false +sp_cleanup.extract_increment=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.hash=false +sp_cleanup.if_condition=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.instanceof=false +sp_cleanup.instanceof_keyword=false +sp_cleanup.invert_equals=false +sp_cleanup.join=false +sp_cleanup.lazy_logical_operator=false +sp_cleanup.make_local_variable_final=true +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.map_cloning=false +sp_cleanup.merge_conditional_blocks=false +sp_cleanup.multi_catch=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.no_string_creation=false +sp_cleanup.no_super=false +sp_cleanup.number_suffix=false +sp_cleanup.objects_equals=false +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false +sp_cleanup.operand_factorization=false +sp_cleanup.organize_imports=true +sp_cleanup.overridden_assignment=false +sp_cleanup.plain_replacement=false +sp_cleanup.precompile_regex=false +sp_cleanup.primitive_comparison=false +sp_cleanup.primitive_parsing=false +sp_cleanup.primitive_rather_than_wrapper=false +sp_cleanup.primitive_serialization=false +sp_cleanup.pull_out_if_from_if_else=false +sp_cleanup.pull_up_assignment=false +sp_cleanup.push_down_negation=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.reduce_indentation=false +sp_cleanup.redundant_comparator=false +sp_cleanup.redundant_falling_through_block_end=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_modifiers=false +sp_cleanup.remove_redundant_semicolons=false +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_array_creation=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.return_expression=false +sp_cleanup.simplify_lambda_expression_and_method_ref=false +sp_cleanup.single_used_field=false +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.standard_comparison=false +sp_cleanup.static_inner_class=false +sp_cleanup.strictly_equal_or_different=false +sp_cleanup.stringbuffer_to_stringbuilder=false +sp_cleanup.stringbuilder=false +sp_cleanup.stringbuilder_for_local_vars=false +sp_cleanup.stringconcat_to_textblock=false +sp_cleanup.substring=false +sp_cleanup.switch=false +sp_cleanup.system_property=false +sp_cleanup.system_property_boolean=false +sp_cleanup.system_property_file_encoding=false +sp_cleanup.system_property_file_separator=false +sp_cleanup.system_property_line_separator=false +sp_cleanup.system_property_path_separator=false +sp_cleanup.ternary_operator=false +sp_cleanup.try_with_resource=false +sp_cleanup.unlooped_while=false +sp_cleanup.unreachable_block=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_autoboxing=false +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_directly_map_method=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_string_is_blank=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true +sp_cleanup.use_unboxing=false +sp_cleanup.use_var=false +sp_cleanup.useless_continue=false +sp_cleanup.useless_return=false +sp_cleanup.valueof_rather_than_instantiation=false diff --git a/importer/restructuredtext/pom.xml b/importer/restructuredtext/pom.xml new file mode 100644 index 000000000..f3fd4cfe7 --- /dev/null +++ b/importer/restructuredtext/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + openfasttrace-importer-restructuredtext + OpenFastTrace reStructuredText Importer + + ../../parent/pom.xml + org.itsallcode.openfasttrace + openfasttrace-parent + ${revision} + + + ${reproducible.build.timestamp} + + + + org.itsallcode.openfasttrace + openfasttrace-api + + + org.itsallcode.openfasttrace + openfasttrace-importer-lightweightmarkup + + + org.itsallcode.openfasttrace + openfasttrace-testutil + test + + + diff --git a/importer/restructuredtext/src/main/java/module-info.java b/importer/restructuredtext/src/main/java/module-info.java new file mode 100644 index 000000000..4892618c3 --- /dev/null +++ b/importer/restructuredtext/src/main/java/module-info.java @@ -0,0 +1,15 @@ +import org.itsallcode.openfasttrace.importer.restructuredtext.RestructuredTextImporterFactory; + +/** + * This provides an importer for the reStructuredText format. + * + * @provides org.itsallcode.openfasttrace.api.importer.ImporterFactory + */ +module org.itsallcode.openfasttrace.importer.restructuredtext +{ + requires transitive org.itsallcode.openfasttrace.api; + requires org.itsallcode.openfasttrace.importer.lightweightmarkup; + + provides org.itsallcode.openfasttrace.api.importer.ImporterFactory + with RestructuredTextImporterFactory; +} diff --git a/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RestructuredTextImporter.java b/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RestructuredTextImporter.java new file mode 100644 index 000000000..e6381f76d --- /dev/null +++ b/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RestructuredTextImporter.java @@ -0,0 +1,172 @@ +package org.itsallcode.openfasttrace.importer.restructuredtext; + +import static org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.LineParserState.*; + +import org.itsallcode.openfasttrace.api.importer.ImportEventListener; +import org.itsallcode.openfasttrace.api.importer.input.InputFile; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.LightWeightMarkupImporter; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.*; + +/** + * Importer for OFT augmented reStructuredText. + *

+ * The purpose of this importer is to find specification items that follow a + * certain structure inside reStructuredText documents. It is not the goal to + * ingest the complete reStructuredText document though, only the specification + * items. Also, the hierarchical structure of the document itself has no impact + * on the specification items. OFT just extracts a flat list. Linking the items + * is explicitly not the purpose of the importer. + *

+ */ +public class RestructuredTextImporter extends LightWeightMarkupImporter +{ + private static final LinePattern SECTION_TITLE = new RstSectionTitlePattern(); + + /** + * Creates a {@link RestructuredTextImporter} object with the given + * parameters. + * + * @param fileName + * the input file to be imported + * @param listener + * the listener to handle import events + */ + RestructuredTextImporter(final InputFile fileName, final ImportEventListener listener) + { + super(fileName, listener); + } + + @Override + protected Transition[] configureTransitions() + { + // @formatter:off + return new Transition[]{ + transition(START , SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(START , START , RstPattern.FORWARD , this::forward ), + transition(START , TITLE , SECTION_TITLE , this::rememberTitle ), + transition(START , START , RstPattern.EVERYTHING , () -> {} ), + + transition(TITLE , TITLE , RstPattern.UNDERLINE , () -> {} ), + transition(TITLE , TITLE , SECTION_TITLE , this::rememberTitle ), + transition(TITLE , SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(TITLE , TITLE , RstPattern.EMPTY , () -> {} ), + transition(TITLE , START , RstPattern.FORWARD , () -> {endItem(); forward();} ), + transition(TITLE , START , RstPattern.EVERYTHING , this::resetTitle ), + + transition(SPEC_ITEM , SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(SPEC_ITEM , SPEC_ITEM , RstPattern.STATUS , this::setStatus ), + transition(SPEC_ITEM , RATIONALE , RstPattern.RATIONALE , this::beginRationale ), + transition(SPEC_ITEM , COMMENT , RstPattern.COMMENT , this::beginComment ), + transition(SPEC_ITEM , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(SPEC_ITEM , COVERS , RstPattern.COVERS , () -> {} ), + transition(SPEC_ITEM , DEPENDS , RstPattern.DEPENDS , () -> {} ), + transition(SPEC_ITEM , NEEDS , RstPattern.NEEDS_INT , this::addNeeds ), + transition(SPEC_ITEM , NEEDS , RstPattern.NEEDS , () -> {} ), + transition(SPEC_ITEM , TAGS , RstPattern.TAGS_INT , this::addTag ), + transition(SPEC_ITEM , TAGS , RstPattern.TAGS , () -> {} ), + transition(SPEC_ITEM , DESCRIPTION, RstPattern.DESCRIPTION, this::beginDescription ), + transition(SPEC_ITEM , DESCRIPTION, RstPattern.NOT_EMPTY , this::beginDescription ), + + transition(DESCRIPTION, SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(DESCRIPTION, TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(DESCRIPTION, RATIONALE , RstPattern.RATIONALE , this::beginRationale ), + transition(DESCRIPTION, COMMENT , RstPattern.COMMENT , this::beginComment ), + transition(DESCRIPTION, COVERS , RstPattern.COVERS , () -> {} ), + transition(DESCRIPTION, DEPENDS , RstPattern.DEPENDS , () -> {} ), + transition(DESCRIPTION, NEEDS , RstPattern.NEEDS_INT , this::addNeeds ), + transition(DESCRIPTION, NEEDS , RstPattern.NEEDS , () -> {} ), + transition(DESCRIPTION, TAGS , RstPattern.TAGS_INT , this::addTag ), + transition(DESCRIPTION, TAGS , RstPattern.TAGS , () -> {} ), + transition(DESCRIPTION, START , RstPattern.FORWARD , () -> {endItem(); forward();} ), + transition(DESCRIPTION, TITLE , SECTION_TITLE , this::rememberTitle ), + transition(DESCRIPTION, DESCRIPTION, RstPattern.EVERYTHING , this::appendDescription ), + + transition(RATIONALE , SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(RATIONALE , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(RATIONALE , COMMENT , RstPattern.COMMENT , this::beginComment ), + transition(RATIONALE , COVERS , RstPattern.COVERS , () -> {} ), + transition(RATIONALE , DEPENDS , RstPattern.DEPENDS , () -> {} ), + transition(RATIONALE , NEEDS , RstPattern.NEEDS_INT , this::addNeeds ), + transition(RATIONALE , NEEDS , RstPattern.NEEDS , () -> {} ), + transition(RATIONALE , TAGS , RstPattern.TAGS_INT , this::addTag ), + transition(RATIONALE , TAGS , RstPattern.TAGS , () -> {} ), + transition(RATIONALE , RATIONALE , RstPattern.EVERYTHING , this::appendRationale ), + + transition(COMMENT , SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(COMMENT , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(COMMENT , COVERS , RstPattern.COVERS , () -> {} ), + transition(COMMENT , DEPENDS , RstPattern.DEPENDS , () -> {} ), + transition(COMMENT , NEEDS , RstPattern.NEEDS_INT , this::addNeeds ), + transition(COMMENT , NEEDS , RstPattern.NEEDS , () -> {} ), + transition(COMMENT , RATIONALE , RstPattern.RATIONALE , this::beginRationale ), + transition(COMMENT , TAGS , RstPattern.TAGS_INT , this::addTag ), + transition(COMMENT , TAGS , RstPattern.TAGS , () -> {} ), + transition(COMMENT , COMMENT , RstPattern.EVERYTHING , this::appendComment ), + + // [impl->dsn~md.covers-list~1] + transition(COVERS , SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(COVERS , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(COVERS , COVERS , RstPattern.COVERS_REF , this::addCoverage ), + transition(COVERS , RATIONALE , RstPattern.RATIONALE , this::beginRationale ), + transition(COVERS , COMMENT , RstPattern.COMMENT , this::beginComment ), + transition(COVERS , DEPENDS , RstPattern.DEPENDS , () -> {} ), + transition(COVERS , NEEDS , RstPattern.NEEDS_INT , this::addNeeds ), + transition(COVERS , NEEDS , RstPattern.NEEDS , () -> {} ), + transition(COVERS , COVERS , RstPattern.EMPTY , () -> {} ), + transition(COVERS , TAGS , RstPattern.TAGS_INT , this::addTag ), + transition(COVERS , TAGS , RstPattern.TAGS , () -> {} ), + transition(COVERS , START , RstPattern.FORWARD , () -> {endItem(); forward();} ), + + // [impl->dsn~md.depends-list~1] + transition(DEPENDS , SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(DEPENDS , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(DEPENDS , DEPENDS , RstPattern.DEPENDS_REF, this::addDependency ), + transition(DEPENDS , RATIONALE , RstPattern.RATIONALE , this::beginRationale ), + transition(DEPENDS , COMMENT , RstPattern.COMMENT , this::beginComment ), + transition(DEPENDS , DEPENDS , RstPattern.DEPENDS , () -> {} ), + transition(DEPENDS , NEEDS , RstPattern.NEEDS_INT , this::addNeeds ), + transition(DEPENDS , NEEDS , RstPattern.NEEDS , () -> {} ), + transition(DEPENDS , DEPENDS , RstPattern.EMPTY , () -> {} ), + transition(DEPENDS , COVERS , RstPattern.COVERS , () -> {} ), + transition(DEPENDS , TAGS , RstPattern.TAGS_INT , this::addTag ), + transition(DEPENDS , TAGS , RstPattern.TAGS , () -> {} ), + transition(DEPENDS , START , RstPattern.FORWARD , () -> {endItem(); forward();} ), + + // [impl->dsn~md.needs-coverage-list-single-line~2] + // [impl->dsn~md.needs-coverage-list~1] + transition(NEEDS , SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(NEEDS , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(NEEDS , RATIONALE , RstPattern.RATIONALE , this::beginRationale ), + transition(NEEDS , COMMENT , RstPattern.COMMENT , this::beginComment ), + transition(NEEDS , DEPENDS , RstPattern.DEPENDS , () -> {} ), + transition(NEEDS , NEEDS , RstPattern.NEEDS_INT , this::addNeeds ), + transition(NEEDS , NEEDS , RstPattern.NEEDS_REF , this::addNeeds ), + transition(NEEDS , NEEDS , RstPattern.EMPTY , () -> {} ), + transition(NEEDS , COVERS , RstPattern.COVERS , () -> {} ), + transition(NEEDS , TAGS , RstPattern.TAGS_INT , this::addTag ), + transition(NEEDS , TAGS , RstPattern.TAGS , () -> {} ), + transition(NEEDS , START , RstPattern.FORWARD , () -> {endItem(); forward();} ), + + transition(TAGS , TAGS , RstPattern.TAG_ENTRY , this::addTag ), + transition(TAGS , TITLE , SECTION_TITLE , () -> {endItem(); rememberTitle();}), + transition(TAGS , SPEC_ITEM , RstPattern.ID , this::beginItem ), + transition(TAGS , RATIONALE , RstPattern.RATIONALE , this::beginRationale ), + transition(TAGS , COMMENT , RstPattern.COMMENT , this::beginComment ), + transition(TAGS , DEPENDS , RstPattern.DEPENDS , () -> {} ), + transition(TAGS , NEEDS , RstPattern.NEEDS_INT , this::addNeeds ), + transition(TAGS , NEEDS , RstPattern.NEEDS , () -> {} ), + transition(TAGS , NEEDS , RstPattern.EMPTY , () -> {} ), + transition(TAGS , COVERS , RstPattern.COVERS , () -> {} ), + transition(TAGS , TAGS , RstPattern.TAGS , () -> {} ), + transition(TAGS , TAGS , RstPattern.TAGS_INT , this::addTag ), + transition(TAGS , START , RstPattern.FORWARD , () -> {endItem(); forward();} ) + }; + // @formatter:on + } + + private static Transition transition(final LineParserState from, final LineParserState to, + final RstPattern pattern, final TransitionAction action) + { + return new Transition(from, to, pattern.getPattern(), action); + } +} diff --git a/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RestructuredTextImporterFactory.java b/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RestructuredTextImporterFactory.java new file mode 100644 index 000000000..cd9b77e21 --- /dev/null +++ b/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RestructuredTextImporterFactory.java @@ -0,0 +1,22 @@ +package org.itsallcode.openfasttrace.importer.restructuredtext; + +import org.itsallcode.openfasttrace.api.importer.*; +import org.itsallcode.openfasttrace.api.importer.input.InputFile; + +/** + * {@link ImporterFactory} for reStructuredText files + */ +public class RestructuredTextImporterFactory extends RegexMatchingImporterFactory +{ + /** Creates a new instance. */ + public RestructuredTextImporterFactory() + { + super("(?i).*\\.rst"); + } + + @Override + public Importer createImporter(final InputFile fileName, final ImportEventListener listener) + { + return new RestructuredTextImporter(fileName, listener); + } +} diff --git a/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RstPattern.java b/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RstPattern.java new file mode 100644 index 000000000..3f855f3d9 --- /dev/null +++ b/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RstPattern.java @@ -0,0 +1,91 @@ +package org.itsallcode.openfasttrace.importer.restructuredtext; + +import org.itsallcode.openfasttrace.api.core.SpecificationItemId; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.ForwardingSpecificationItem; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.LinePattern; +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.SimpleLinePattern; + +/** + * Patterns that describe tokens to be recognized within reStructured Text + * specifications. + */ +enum RstPattern +{ + // [impl->dsn~md.specification-item-title~1] + // [impl->dsn~md.artifact-forwarding-notation~1] + + // @formatter:off + COMMENT("Comment:\\s*"), + COVERS("Covers:\\s*"), + COVERS_REF(PatternConstants.REFERENCE_AFTER_BULLET), + DEPENDS("Depends:\\s*"), + DEPENDS_REF(PatternConstants.REFERENCE_AFTER_BULLET), + DESCRIPTION("Description:\\s*"), + EMPTY("(\\s*)"), + EVERYTHING("(.*)"), + FORWARD(".*?(" + + PatternConstants.ARTIFACT_TYPE + + "\\s*" + + ForwardingSpecificationItem.FORWARD_MARKER + + "\\s*" + + PatternConstants.ARTIFACT_TYPE + + "(?:,\\s*" + + PatternConstants.ARTIFACT_TYPE + + ")*" + + "\\s*" + + ForwardingSpecificationItem.ORIGINAL_MARKER + + "\\s*" + + SpecificationItemId.ID_PATTERN + + ").*?"), + ID("`?(" + SpecificationItemId.ID_PATTERN + ")`?.*"), + NEEDS_INT("Needs:(\\s*\\w+\\s*(?:,\\s*\\w+\\s*)*)"), + NEEDS("Needs:\\s*"), + NEEDS_REF(PatternConstants.UP_TO_3_WHITESPACES + PatternConstants.BULLETS + + "(?:.*\\W)?" // + + "(\\p{Alpha}+)" // + + "(?:\\W.*)?"), + NOT_EMPTY("([^\n\r]+)"), + RATIONALE("Rationale:\\s*"), + STATUS("Status:\\s*(approved|proposed|draft)\\s*"), + TAGS_INT("Tags:(\\s*\\w+\\s*(?:,\\s*\\w+\\s*)*)"), + TAGS("Tags:\\s*"), + TAG_ENTRY(PatternConstants.UP_TO_3_WHITESPACES + PatternConstants.BULLETS + + "\\s*" // + + "(.*)"), + UNDERLINE("([-=`:.'\"~^_*+#<>]{3,})\\s*"); + // @formatter:on + + private final LinePattern pattern; + + RstPattern(final String regularExpression) + { + this.pattern = SimpleLinePattern.of(regularExpression); + } + + /** + * Get the regular expression pattern object + * + * @return the pattern + */ + public LinePattern getPattern() + { + return this.pattern; + } + + private static final class PatternConstants + { + public static final String ARTIFACT_TYPE = "[a-zA-Z]+"; + public static final String BULLETS = "[+*-]"; + private static final String UP_TO_3_WHITESPACES = "\\s{0,3}"; + // [impl->dsn~md.requirement-references~1] + public static final String REFERENCE_AFTER_BULLET = UP_TO_3_WHITESPACES + + PatternConstants.BULLETS + "(?:.*\\W)?" // + + "(" + SpecificationItemId.ID_PATTERN + ")" // + + "(?:\\W.*)?"; + + private PatternConstants() + { + // not instantiable + } + } +} diff --git a/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RstSectionTitlePattern.java b/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RstSectionTitlePattern.java new file mode 100644 index 000000000..aa866d663 --- /dev/null +++ b/importer/restructuredtext/src/main/java/org/itsallcode/openfasttrace/importer/restructuredtext/RstSectionTitlePattern.java @@ -0,0 +1,21 @@ +package org.itsallcode.openfasttrace.importer.restructuredtext; + +import java.util.List; +import java.util.Optional; + +import org.itsallcode.openfasttrace.importer.lightweightmarkup.statemachine.LinePattern; + +class RstSectionTitlePattern implements LinePattern +{ + private static final LinePattern UNDERLINE = RstPattern.UNDERLINE.getPattern(); + + @Override + public Optional> getMatches(final String line, final String nextLine) + { + if (line != null && nextLine != null && UNDERLINE.getMatches(nextLine, null).isPresent()) + { + return Optional.of(List.of(line)); + } + return Optional.empty(); + } +} diff --git a/importer/restructuredtext/src/main/resources/META-INF/services/org.itsallcode.openfasttrace.api.importer.ImporterFactory b/importer/restructuredtext/src/main/resources/META-INF/services/org.itsallcode.openfasttrace.api.importer.ImporterFactory new file mode 100644 index 000000000..2f1296348 --- /dev/null +++ b/importer/restructuredtext/src/main/resources/META-INF/services/org.itsallcode.openfasttrace.api.importer.ImporterFactory @@ -0,0 +1 @@ +org.itsallcode.openfasttrace.importer.restructuredtext.RestructuredTextImporterFactory diff --git a/importer/restructuredtext/src/test/java/org/itsallcode/openfasttrace/importer/restructuredtext/RstSectionTitlePatternTest.java b/importer/restructuredtext/src/test/java/org/itsallcode/openfasttrace/importer/restructuredtext/RstSectionTitlePatternTest.java new file mode 100644 index 000000000..82e04e7f6 --- /dev/null +++ b/importer/restructuredtext/src/test/java/org/itsallcode/openfasttrace/importer/restructuredtext/RstSectionTitlePatternTest.java @@ -0,0 +1,93 @@ +package org.itsallcode.openfasttrace.importer.restructuredtext; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class RstSectionTitlePatternTest +{ + + static Stream testCases() + { + return Stream.of( + titleNotRecongnized(null, null), + titleNotRecongnized(null, "ignored"), + titleNotRecongnized(null, "===="), + titleNotRecongnized("ignored", null), + titleNotRecongnized("# Title", null), + titleNotRecongnized("## Title", null), + titleNotRecongnized("# Title", "ignored"), + testCase("# Title", "=======", "# Title"), + testCase("Title with words", "=======", "Title with words"), + testCase("\t Leading & trailing whitespace not removed ", "=======", + "\t Leading & trailing whitespace not removed "), + underlineRecognized("==="), + underlineRecognized("---"), + underlineRecognized("___"), + underlineRecognized(":::"), + underlineRecognized("..."), + underlineRecognized("^^^"), + underlineRecognized("```"), + underlineRecognized("\"\"\""), + underlineRecognized("'''"), + underlineRecognized("^^^"), + underlineRecognized("~~~"), + underlineRecognized("***"), + underlineRecognized("+++"), + underlineRecognized("###"), + underlineRecognized("<<<"), + underlineRecognized(">>>"), + underlineRecognized("======="), + underlineNotRecognized("=="), + underlineNotRecognized("--"), + underlineNotRecognized("__"), + underlineNotRecognized("^^")); + } + + private static Arguments underlineRecognized(final String underline) + { + return Arguments.of("Title", underline, "Title"); + } + + private static Arguments underlineNotRecognized(final String underline) + { + return Arguments.of("Title", underline, null); + } + + private static Arguments titleNotRecongnized(final String line, final String nextLine) + { + return testCase(line, nextLine, null); + } + + private static Arguments testCase(final String line, final String nextLine, final String expected) + { + return Arguments.of(line, nextLine, expected); + } + + @ParameterizedTest + @MethodSource("testCases") + void test(final String line, final String nextLine, final String expected) + { + final RstSectionTitlePattern pattern = new RstSectionTitlePattern(); + final Optional> result = pattern.getMatches(line, nextLine); + if (expected == null) + { + assertThat("Lines '" + line + "' + '" + nextLine + "' should not be recognized as a section title", + result.isPresent(), is(false)); + } + else + { + assertAll(() -> assertThat( + "Lines '" + line + "' + '" + nextLine + "' should be recognized as a section title", + result.isPresent(), is(true)), () -> assertThat(result.get().get(0), is(expected))); + } + } +} diff --git a/importer/restructuredtext/src/test/java/org/itsallcode/openfasttrace/importer/restructuredtext/TestRestructuredTextImporter.java b/importer/restructuredtext/src/test/java/org/itsallcode/openfasttrace/importer/restructuredtext/TestRestructuredTextImporter.java new file mode 100644 index 000000000..5a733a769 --- /dev/null +++ b/importer/restructuredtext/src/test/java/org/itsallcode/openfasttrace/importer/restructuredtext/TestRestructuredTextImporter.java @@ -0,0 +1,146 @@ +package org.itsallcode.openfasttrace.importer.restructuredtext; + +import static org.itsallcode.matcher.auto.AutoMatcher.contains; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.item; + +import org.itsallcode.openfasttrace.api.core.SpecificationItemId; +import org.itsallcode.openfasttrace.api.importer.ImporterFactory; +import org.itsallcode.openfasttrace.testutil.importer.lightweightmarkup.AbstractLightWeightMarkupImporterTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class TestRestructuredTextImporter extends AbstractLightWeightMarkupImporterTest +{ + private static final ImporterFactory importerFactory = new RestructuredTextImporterFactory(); + + TestRestructuredTextImporter() + { + super(1); + } + + @Override + protected ImporterFactory getImporterFactory() + { + return importerFactory; + } + + protected String formatTitle(final String title, final int level) + { + return title + "\n" + "=".repeat(title.length()); + } + + // [utest -> dsn~md.specification-item-title~1] + @Test + void testMarkdownTitleBeforeRequirementIdIsRequirementTitle() + { + assertImport("titles.md", + """ + The title + ========= + the~id~1 + """, + contains(item() + .id("the", "id", 1) + .title("The title") + .location("titles.md", 3) + .build())); + } + + // [utest -> dsn~md.specification-item-title~1] + @Test + void testMarkdownTitleDetectedAfterAnotherTitle() + { + assertImport("more_titles.md", + """ + 1st level title + =============== + + 2nd level title + --------------- + + the~id~1 + """, + contains(item() + .title("2nd level title") + .id("the", "id", 1) + .location("more_titles.md", 7) + .build())); + } + + // [utest->dsn~md.specification-item-title~1] + @Test + void testFindTitleAfterTitle() + { + assertImport("x", """ + This title should be ignored + ============================ + + Title + ----- + `a~b~1 + """, + contains(item() + .id(SpecificationItemId.parseId("a~b~1")) + .title("Title").location("x", 6) + .build())); + } + + @ParameterizedTest + @ValueSource(strings = + { "---------------------------------", "---", "===", "======", "--- ", + "=== ", "---\t" }) + void testRecognizeItemTitleWithUnderlines(final String underline) + { + assertImport("file name", """ + This is a title with an underline + %s + `extra~support-underlined-headers~1` + Body text. + """.formatted(underline), + contains(item() + .id(SpecificationItemId.createId("extra", "support-underlined-headers", + 1)) + .title("This is a title with an underline") + .description("Body text.") + .location("file name", 3) + .build())); + } + + @ValueSource(strings = { "---------------------------------", "---", "===", "======", + "================================================", + "--- ", "=== ", "---\t" + }) + @ParameterizedTest + void testRecognizeItemTitleWithUnderlinesAfterAnotherTitle(final String underline) + { + assertImport("y", """ + # This must be ignored. + This is a title with an underline + %s + `extra~support-underlined-headers~1` + Body text. + """.formatted(underline), + contains(item() + .id(SpecificationItemId.createId("extra", "support-underlined-headers", + 1)) + .title("This is a title with an underline") + .description("Body text.") + .location("y", 4) + .build())); + } + + @Test + void testLessThenThreeUnderliningCharactersAreNotDetectedAsTitleUnderlines() + { + assertImport("z", """ + This is not a title since the underline is too short + -- + req~too-short~111 + """, + contains(item() + .id(SpecificationItemId.createId("req", "too-short", 111)) + .location("z", 3) + .build())); + } +} diff --git a/importer/restructuredtext/src/test/java/org/itsallcode/openfasttrace/importer/restructuredtext/TestRestructuredTextImporterFactory.java b/importer/restructuredtext/src/test/java/org/itsallcode/openfasttrace/importer/restructuredtext/TestRestructuredTextImporterFactory.java new file mode 100644 index 000000000..d42528ae0 --- /dev/null +++ b/importer/restructuredtext/src/test/java/org/itsallcode/openfasttrace/importer/restructuredtext/TestRestructuredTextImporterFactory.java @@ -0,0 +1,31 @@ +package org.itsallcode.openfasttrace.importer.restructuredtext; + +import org.itsallcode.openfasttrace.testutil.importer.ImporterFactoryTestBase; + +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * Tests for {@link RestructuredTextImporterFactory} + */ +class TestRestructuredTextImporterFactory extends ImporterFactoryTestBase +{ + @Override + protected RestructuredTextImporterFactory createFactory() + { + return new RestructuredTextImporterFactory(); + } + + @Override + protected List getSupportedFilenames() + { + return asList("file.rst", "file.RST", "FILE.rst", "FILE.RST"); + } + + @Override + protected List getUnsupportedFilenames() + { + return asList("file.rs", "file.rest", "filerst", "file.rst."); + } +} diff --git a/importer/restructuredtext/src/test/resources/logging.properties b/importer/restructuredtext/src/test/resources/logging.properties new file mode 100644 index 000000000..b2c9cbc7e --- /dev/null +++ b/importer/restructuredtext/src/test/resources/logging.properties @@ -0,0 +1,11 @@ +handlers = java.util.logging.ConsoleHandler org.itsallcode.openfasttrace.testutil.log.NoOpLoggingHandler +.level = INFO +java.util.logging.ConsoleHandler.level = ALL +java.util.logging.ConsoleHandler.formatter = org.itsallcode.openfasttrace.testutil.log.ShortClassNameFormatter +java.util.logging.ConsoleHandler.encoding = UTF-8 + +org.itsallcode.openfasttrace.testutil.log.NoOpLoggingHandler.level = ALL + +# Set this to FINEST for debugging the state machine +org.itsallcode.openfasttrace.testutil.level = FINE +org.itsallcode.openfasttrace.importer.level = FINE diff --git a/importer/specobject/.settings/org.eclipse.jdt.core.prefs b/importer/specobject/.settings/org.eclipse.jdt.core.prefs index 04b36440c..a40522564 100644 --- a/importer/specobject/.settings/org.eclipse.jdt.core.prefs +++ b/importer/specobject/.settings/org.eclipse.jdt.core.prefs @@ -1,6 +1,6 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning @@ -20,7 +20,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/importer/tag/.settings/org.eclipse.jdt.core.prefs b/importer/tag/.settings/org.eclipse.jdt.core.prefs index 04b36440c..a40522564 100644 --- a/importer/tag/.settings/org.eclipse.jdt.core.prefs +++ b/importer/tag/.settings/org.eclipse.jdt.core.prefs @@ -1,6 +1,6 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning @@ -20,7 +20,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporter.java b/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporter.java index 7fddf3af2..0454ffc86 100644 --- a/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporter.java +++ b/importer/tag/src/test/java/org/itsallcode/openfasttrace/importer/tag/TestTagImporter.java @@ -208,7 +208,7 @@ private List runImporter(final String content) * Create a test case using the content as input for the * {@link TagImporter}. Make sure to concatenate the content to avoid * breaking self-tracing. - * + * * @param content * content to parse * @param itemBuilder diff --git a/importer/xmlparser/.settings/org.eclipse.jdt.core.prefs b/importer/xmlparser/.settings/org.eclipse.jdt.core.prefs index 6d1f31917..e2b1419eb 100644 --- a/importer/xmlparser/.settings/org.eclipse.jdt.core.prefs +++ b/importer/xmlparser/.settings/org.eclipse.jdt.core.prefs @@ -10,9 +10,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable.secondary= org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -129,7 +129,7 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/importer/zip/.settings/org.eclipse.jdt.core.prefs b/importer/zip/.settings/org.eclipse.jdt.core.prefs index 439aefe45..ffe8a6a5c 100644 --- a/importer/zip/.settings/org.eclipse.jdt.core.prefs +++ b/importer/zip/.settings/org.eclipse.jdt.core.prefs @@ -1,9 +1,9 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -28,7 +28,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/oft-self-trace.sh b/oft-self-trace.sh index 77fc91c33..86d3c35d5 100755 --- a/oft-self-trace.sh +++ b/oft-self-trace.sh @@ -15,7 +15,9 @@ if $oft_script trace \ --output-file "$report_file" \ --output-format html \ "$base_dir/doc/spec" \ + "$base_dir/importer/lightweightmarkup/src" \ "$base_dir/importer/markdown/src" \ + "$base_dir/importer/restructuredtext/src" \ "$base_dir/importer/specobject/src" \ "$base_dir/importer/zip/src" \ "$base_dir/importer/tag/src" \ diff --git a/parent/pom.xml b/parent/pom.xml index 2b409fdb1..21b187026 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -8,8 +8,8 @@ Free requirement tracking suite https://github.com/itsallcode/openfasttrace - 3.8.0 - 11 + 4.0.0 + 17 5.11.0-M1 3.2.5 UTF-8 @@ -129,12 +129,24 @@ ${revision} compile + + org.itsallcode.openfasttrace + openfasttrace-importer-lightweightmarkup + ${revision} + compile + org.itsallcode.openfasttrace openfasttrace-importer-markdown ${revision} compile + + org.itsallcode.openfasttrace + openfasttrace-importer-restructuredtext + ${revision} + compile + org.itsallcode.openfasttrace openfasttrace-importer-specobject diff --git a/pom.xml b/pom.xml index 206a73ab2..925234683 100644 --- a/pom.xml +++ b/pom.xml @@ -21,8 +21,10 @@ product exporter/common exporter/specobject + importer/lightweightmarkup importer/markdown importer/xmlparser + importer/restructuredtext importer/specobject importer/tag importer/zip diff --git a/product/.settings/org.eclipse.jdt.core.prefs b/product/.settings/org.eclipse.jdt.core.prefs index e2f103ea8..a975639ee 100644 --- a/product/.settings/org.eclipse.jdt.core.prefs +++ b/product/.settings/org.eclipse.jdt.core.prefs @@ -10,9 +10,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable.secondary= org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate @@ -127,7 +127,7 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/product/pom.xml b/product/pom.xml index 519f449b6..ced143151 100644 --- a/product/pom.xml +++ b/product/pom.xml @@ -23,15 +23,15 @@ org.itsallcode.openfasttrace - openfasttrace-exporter-common + openfasttrace-exporter-specobject org.itsallcode.openfasttrace - openfasttrace-exporter-specobject + openfasttrace-importer-markdown org.itsallcode.openfasttrace - openfasttrace-importer-markdown + openfasttrace-importer-restructuredtext org.itsallcode.openfasttrace @@ -125,4 +125,4 @@ - + \ No newline at end of file diff --git a/product/src/test/java/org/itsallcode/openfasttrace/cli/TestCliStarter.java b/product/src/test/java/org/itsallcode/openfasttrace/cli/TestCliStarter.java index fffc8ad0b..c831a9bf2 100644 --- a/product/src/test/java/org/itsallcode/openfasttrace/cli/TestCliStarter.java +++ b/product/src/test/java/org/itsallcode/openfasttrace/cli/TestCliStarter.java @@ -68,7 +68,7 @@ void testNoArguments(@SysErr final Capturable err) private void assertExitWithError(final Runnable runnable, final ExitStatus status, final String message, final Capturable stream) throws MultipleFailuresError { - stream.capture(); + stream.captureMuted(); assertAll( // () -> assertExitWithStatus(status.getCode(), runnable), () -> assertThat(stream.getCapturedData(), startsWith(message)) // @@ -100,7 +100,7 @@ void testConvertWithoutExplicitInputs(@SysOut final Capturable out) private void assertExitOkWithStdOutStart(final Runnable runnable, final String outputStart, final Capturable out) throws MultipleFailuresError { - out.capture(); + out.captureMuted(); assertAll(() -> assertExitWithStatus(ExitStatus.OK.getCode(), runnable), () -> assertOutputFileExists(false), // () -> assertThat(out.getCapturedData(), startsWith(outputStart))); } @@ -215,7 +215,7 @@ void testTraceWithReportVerbosityQuietToStdOut(@SysOut final Capturable out) thr TRACE_COMMAND, this.DOC_DIR.toString(), // REPORT_VERBOSITY_PARAMETER, "QUIET" // ); - out.capture(); + out.captureMuted(); assertAll( // () -> assertExitWithStatus(ExitStatus.OK.getCode(), runnable), // () -> assertOutputFileExists(false), diff --git a/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java b/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java index 98962c4b4..4925686eb 100644 --- a/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java +++ b/product/src/test/java/org/itsallcode/openfasttrace/core/serviceloader/TestInitializingServiceLoader.java @@ -14,6 +14,7 @@ import org.itsallcode.openfasttrace.api.importer.ImporterFactory; import org.itsallcode.openfasttrace.exporter.specobject.SpecobjectExporterFactory; import org.itsallcode.openfasttrace.importer.markdown.MarkdownImporterFactory; +import org.itsallcode.openfasttrace.importer.restructuredtext.RestructuredTextImporterFactory; import org.itsallcode.openfasttrace.importer.specobject.SpecobjectImporterFactory; import org.itsallcode.openfasttrace.importer.tag.TagImporterFactory; import org.itsallcode.openfasttrace.importer.zip.ZipFileImporterFactory; @@ -36,15 +37,15 @@ void testNoServicesRegistered() assertThat(voidServiceLoader, emptyIterable()); } - @SuppressWarnings("unchecked") @Test void testImporterFactoriesRegistered() { final ImporterContext context = new ImporterContext(null); final List services = getRegisteredServices(ImporterFactory.class, context); - assertThat(services, hasSize(4)); - assertThat(services, contains(instanceOf(MarkdownImporterFactory.class), // + assertThat(services, hasSize(5)); + assertThat(services, containsInAnyOrder(instanceOf(MarkdownImporterFactory.class), // + instanceOf(RestructuredTextImporterFactory.class), // instanceOf(SpecobjectImporterFactory.class), // instanceOf(TagImporterFactory.class), // instanceOf(ZipFileImporterFactory.class))); diff --git a/product/src/test/java/org/itsallcode/openfasttrace/report/TestReportService.java b/product/src/test/java/org/itsallcode/openfasttrace/report/TestReportService.java index 266ad97f0..d91452257 100644 --- a/product/src/test/java/org/itsallcode/openfasttrace/report/TestReportService.java +++ b/product/src/test/java/org/itsallcode/openfasttrace/report/TestReportService.java @@ -1,7 +1,6 @@ package org.itsallcode.openfasttrace.report; import static org.hamcrest.MatcherAssert.assertThat; - import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -16,9 +15,7 @@ import org.itsallcode.openfasttrace.api.ReportSettings; import org.itsallcode.openfasttrace.api.core.Trace; import org.itsallcode.openfasttrace.api.exporter.ExporterException; -import org.itsallcode.openfasttrace.api.report.ReportException; -import org.itsallcode.openfasttrace.api.report.ReportVerbosity; -import org.itsallcode.openfasttrace.api.report.ReporterContext; +import org.itsallcode.openfasttrace.api.report.*; import org.itsallcode.openfasttrace.core.report.ReportService; import org.itsallcode.openfasttrace.core.report.ReporterFactoryLoader; import org.junit.jupiter.api.Test; @@ -41,7 +38,7 @@ void testReportPlainText(@SysOut final Capturable out) .builder() // .verbosity(ReportVerbosity.MINIMAL) // .build(); - out.capture(); + out.captureMuted(); createService(settings).reportTraceToStdOut(this.traceMock, settings.getOutputFormat()); assertThat(out.getCapturedData(), equalTo("not ok\n")); } @@ -53,7 +50,7 @@ void testReportHtml(@SysOut final Capturable out) .builder() // .outputFormat("html") // .build(); - out.capture(); + out.captureMuted(); createService(settings).reportTraceToStdOut(this.traceMock, settings.getOutputFormat()); assertThat(out.getCapturedData(), startsWith("")); } @@ -117,4 +114,4 @@ private void makeFileWritable(final Path readOnlyFilePath) { readOnlyFilePath.toFile().setWritable(true); } -} \ No newline at end of file +} diff --git a/reporter/aspec/.settings/org.eclipse.jdt.core.prefs b/reporter/aspec/.settings/org.eclipse.jdt.core.prefs index 2de9d8bc1..c5b728dbf 100644 --- a/reporter/aspec/.settings/org.eclipse.jdt.core.prefs +++ b/reporter/aspec/.settings/org.eclipse.jdt.core.prefs @@ -8,8 +8,8 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nullable.secondary= org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.APILeak=warning org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning @@ -119,7 +119,7 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/reporter/html/.settings/org.eclipse.jdt.core.prefs b/reporter/html/.settings/org.eclipse.jdt.core.prefs index 2de9d8bc1..c5b728dbf 100644 --- a/reporter/html/.settings/org.eclipse.jdt.core.prefs +++ b/reporter/html/.settings/org.eclipse.jdt.core.prefs @@ -8,8 +8,8 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nullable.secondary= org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.APILeak=warning org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning @@ -119,7 +119,7 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/reporter/html/src/main/java/module-info.java b/reporter/html/src/main/java/module-info.java index c27dad5c4..741ecc78b 100644 --- a/reporter/html/src/main/java/module-info.java +++ b/reporter/html/src/main/java/module-info.java @@ -1,5 +1,5 @@ /** - * This provides an report generator for the HTML format. + * This provides a report generator for the HTML format. * * @provides org.itsallcode.openfasttrace.api.report.ReporterFactory */ diff --git a/reporter/html/src/test/java/org/itsallcode/openfasttrace/report/html/TestHtmlReport.java b/reporter/html/src/test/java/org/itsallcode/openfasttrace/report/html/TestHtmlReport.java index 49f0bb28c..fb6e69fad 100644 --- a/reporter/html/src/test/java/org/itsallcode/openfasttrace/report/html/TestHtmlReport.java +++ b/reporter/html/src/test/java/org/itsallcode/openfasttrace/report/html/TestHtmlReport.java @@ -18,6 +18,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.itemWithId; + @ExtendWith(MockitoExtension.class) class TestHtmlReport { @@ -48,13 +50,11 @@ protected String renderToString() void testRenderSimpleTrace() { final LinkedSpecificationItem itemA = new LinkedSpecificationItem( - SpecificationItem.builder() // - .id(SpecificationItemId.createId("a", "a-item", 1)) // + itemWithId(SpecificationItemId.createId("a", "a-item", 1)) // .description("Description A") // .build()); final LinkedSpecificationItem itemB = new LinkedSpecificationItem( - SpecificationItem.builder() // - .id(SpecificationItemId.createId("b", "b-item", 1)) // + itemWithId(SpecificationItemId.createId("b", "b-item", 1)) // .description("Description b") // .build()); when(this.traceMock.getItems()).thenReturn(Arrays.asList(itemA, itemB)); diff --git a/reporter/html/src/test/java/org/itsallcode/openfasttrace/report/html/view/html/TestHtmlSpecificationItem.java b/reporter/html/src/test/java/org/itsallcode/openfasttrace/report/html/view/html/TestHtmlSpecificationItem.java index 792dba2d3..ab76227ad 100644 --- a/reporter/html/src/test/java/org/itsallcode/openfasttrace/report/html/view/html/TestHtmlSpecificationItem.java +++ b/reporter/html/src/test/java/org/itsallcode/openfasttrace/report/html/view/html/TestHtmlSpecificationItem.java @@ -22,6 +22,8 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.itemWithId; + @ExtendWith(MockitoExtension.class) class TestHtmlSpecificationItem extends AbstractTestHtmlRenderer { @@ -54,8 +56,7 @@ public void prepareEachTest() @Test void testRenderMinimalItem() { - final SpecificationItem item = SpecificationItem.builder() // - .id(ITEM_A_ID) // + final SpecificationItem item = itemWithId(ITEM_A_ID) // .title("Item A title") // .description("Single line description") // .build(); @@ -80,8 +81,7 @@ void testRenderDetailsDefaultValue(final DetailsSectionDisplay displayStatus, fi { this.factory = HtmlViewFactory.create(this.outputStream, HtmlReport.getCssUrl(), displayStatus); - final SpecificationItem item = SpecificationItem.builder() // - .id(ITEM_A_ID) // + final SpecificationItem item = itemWithId(ITEM_A_ID) // .title("Item A title") // .description("Single line description") // .build(); @@ -92,8 +92,7 @@ void testRenderDetailsDefaultValue(final DetailsSectionDisplay displayStatus, fi @Test void testRenderMultiLineItem() { - final SpecificationItem item = SpecificationItem.builder() // - .id(ITEM_B_ID) // + final SpecificationItem item = itemWithId(ITEM_B_ID) // .title("Item B title") // .description("Description A\n\nDescription B") // .rationale("Rationale A\n\nRationale B") // @@ -127,8 +126,7 @@ protected void renderItemOnIndentationLevel(final SpecificationItem item, @Test void testRenderNeeds() { - final SpecificationItem item = SpecificationItem.builder() // - .id(ITEM_A_ID) // + final SpecificationItem item = itemWithId(ITEM_A_ID) // .addNeedsArtifactType(IMPL) // .addNeedsArtifactType(ITEST) // .addNeedsArtifactType(UTEST) // @@ -151,8 +149,7 @@ void testRenderNeeds() @Test void testRenderIncomingLinks() { - final SpecificationItem item = SpecificationItem.builder() // - .id(ITEM_A_ID) // + final SpecificationItem item = itemWithId(ITEM_A_ID) // .build(); final LinkedSpecificationItem linkedItem = new LinkedSpecificationItem(item); linkedItem.addLinkToItemWithStatus(this.itemMockB, LinkStatus.COVERED_SHALLOW); @@ -181,8 +178,7 @@ void testRenderIncomingLinks() @Test void testRenderOutgoingLinks() { - final SpecificationItem item = SpecificationItem.builder() // - .id(ITEM_A_ID) // + final SpecificationItem item = itemWithId(ITEM_A_ID) // .build(); final LinkedSpecificationItem linkedItem = new LinkedSpecificationItem(item); linkedItem.addLinkToItemWithStatus(this.itemMockB, LinkStatus.COVERS); @@ -212,13 +208,12 @@ void testRenderOutgoingLinks() void testRenderOrigin() { final Location location = Location.create("foo/bar", 13); - final SpecificationItem item = SpecificationItem.builder() // - .id(ITEM_A_ID) // + final SpecificationItem item = itemWithId(ITEM_A_ID) // .location(location) // .build(); final LinkedSpecificationItem linkedItem = new LinkedSpecificationItem(item); - final SpecificationItem subItem = SpecificationItem.builder() // - .id(ITEM_B_ID).location(Location.create("http://example.org/foo.txt", 3)).build(); + final SpecificationItem subItem = itemWithId(ITEM_B_ID) + .location(Location.create("http://example.org/foo.txt", 3)).build(); final LinkedSpecificationItem linkedSubItem = new LinkedSpecificationItem(subItem); linkedItem.addLinkToItemWithStatus(linkedSubItem, LinkStatus.COVERED_SHALLOW); final Viewable view = this.factory.createSpecificationItem(linkedItem); @@ -276,7 +271,7 @@ void testRenderEscapesHtmlElementsInRationale() private SpecificationItem.Builder defaultItem() { - return SpecificationItem.builder().id(ITEM_A_ID); + return itemWithId(ITEM_A_ID); } private void assertRender(final SpecificationItem.Builder itemBuilder, final Matcher matcher) diff --git a/reporter/plaintext/.settings/org.eclipse.jdt.core.prefs b/reporter/plaintext/.settings/org.eclipse.jdt.core.prefs index 04b36440c..a40522564 100644 --- a/reporter/plaintext/.settings/org.eclipse.jdt.core.prefs +++ b/reporter/plaintext/.settings/org.eclipse.jdt.core.prefs @@ -1,6 +1,6 @@ eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning @@ -20,7 +20,7 @@ org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/testutil/.settings/org.eclipse.jdt.core.prefs b/testutil/.settings/org.eclipse.jdt.core.prefs index 752ef0855..5bb3d14ee 100644 --- a/testutil/.settings/org.eclipse.jdt.core.prefs +++ b/testutil/.settings/org.eclipse.jdt.core.prefs @@ -8,8 +8,8 @@ org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable org.eclipse.jdt.core.compiler.annotation.nullable.secondary= org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=11 -org.eclipse.jdt.core.compiler.compliance=11 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.APILeak=warning org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning @@ -119,7 +119,7 @@ org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning org.eclipse.jdt.core.compiler.processAnnotations=disabled org.eclipse.jdt.core.compiler.release=disabled -org.eclipse.jdt.core.compiler.source=11 +org.eclipse.jdt.core.compiler.source=17 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false diff --git a/testutil/pom.xml b/testutil/pom.xml index 4be5836cd..b98572f38 100644 --- a/testutil/pom.xml +++ b/testutil/pom.xml @@ -25,6 +25,11 @@ junit-jupiter-api compile + + org.junit.jupiter + junit-jupiter-params + compile + org.hamcrest hamcrest @@ -53,6 +58,8 @@ -Xlint:-exports -Xlint:-requires-automatic + + -Xlint:-requires-transitive-automatic -Werror @@ -61,6 +68,7 @@ org.apache.maven.plugins maven-javadoc-plugin + true diff --git a/testutil/src/main/java/module-info.java b/testutil/src/main/java/module-info.java index f191b7f54..ab554b94b 100644 --- a/testutil/src/main/java/module-info.java +++ b/testutil/src/main/java/module-info.java @@ -7,14 +7,17 @@ exports org.itsallcode.openfasttrace.testutil.cli; exports org.itsallcode.openfasttrace.testutil.core; exports org.itsallcode.openfasttrace.testutil.importer; + exports org.itsallcode.openfasttrace.testutil.importer.lightweightmarkup; exports org.itsallcode.openfasttrace.testutil.log; exports org.itsallcode.openfasttrace.testutil.matcher; exports org.itsallcode.openfasttrace.testutil.xml; requires org.hamcrest; + requires transitive org.itsallcode.automatcher; requires transitive org.junit.jupiter.api; - requires org.mockito; - requires org.mockito.junit.jupiter; + requires transitive org.junit.jupiter.params; + requires transitive org.mockito; + requires transitive org.mockito.junit.jupiter; requires java.logging; requires transitive java.xml; requires org.itsallcode.openfasttrace.api; diff --git a/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/core/ItemBuilderFactory.java b/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/core/ItemBuilderFactory.java new file mode 100644 index 000000000..f7055e84c --- /dev/null +++ b/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/core/ItemBuilderFactory.java @@ -0,0 +1,44 @@ +package org.itsallcode.openfasttrace.testutil.core; + +import org.itsallcode.openfasttrace.api.core.SpecificationItem; +import org.itsallcode.openfasttrace.api.core.SpecificationItemId; + +/** + * The {@link ItemBuilderFactory} class provides convenience methods for creating instances of {@link SpecificationItem}. + */ +public class ItemBuilderFactory { + private ItemBuilderFactory() { + // prevent instantiation. + } + + /** + * Creates a new instance of {@link SpecificationItem.Builder}. + * + * @return a new instance of {@link SpecificationItem.Builder} + */ + public static final SpecificationItem.Builder item() { + return SpecificationItem.builder(); + } + + /** + * Creates a new instance of {@link SpecificationItem.Builder} with the specified ID. + * + * @param id ID of the specification item + * + * @return a new instance of {@link SpecificationItem.Builder} with the specified ID + */ + public static final SpecificationItem.Builder itemWithId(SpecificationItemId id) { + return SpecificationItem.builder().id(id); + } + + /** + * Returns a new instance of {@link SpecificationItem.Builder} with the default filename and the specified line. + * + * @param line line number of the specification item in the file + * + * @return a new instance of {@link SpecificationItem.Builder} with the default filename and the specified line + */ + public static final SpecificationItem.Builder itemWithDefaultFilenameInLine(final int line) { + return SpecificationItem.builder().location("file", line); + } +} diff --git a/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/importer/ImportAssertions.java b/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/importer/ImportAssertions.java new file mode 100644 index 000000000..a8244e751 --- /dev/null +++ b/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/importer/ImportAssertions.java @@ -0,0 +1,54 @@ +package org.itsallcode.openfasttrace.testutil.importer; + +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.BufferedReader; +import java.io.StringReader; +import java.nio.file.Path; +import java.util.List; +import java.util.logging.Logger; + +import org.hamcrest.Matcher; +import org.itsallcode.openfasttrace.api.core.SpecificationItem; +import org.itsallcode.openfasttrace.api.importer.*; +import org.itsallcode.openfasttrace.api.importer.input.InputFile; +import org.itsallcode.openfasttrace.testutil.importer.input.StreamInput; + +public final class ImportAssertions +{ + private static final Logger LOGGER = Logger.getLogger(ImportAssertions.class.getName()); + + private ImportAssertions() + { + // Prevent instantiation. + } + + /** + * Assert that the imported input matches the given matcher and filename. + * + * @param path + * expected filename + * @param input + * content to be imported + * @param matcher + * matcher that defines expectation for imported data + */ + public static void assertImportWithFactory(final Path path, final String input, + final Matcher> matcher, + final ImporterFactory importerFactory) + { + assertThat(runImporterOnText(path, input, importerFactory), matcher); + } + + public static List runImporterOnText(final Path path, final String text, + final ImporterFactory importerFactory) + { + LOGGER.finest("Importing text: ***\n" + text + "\n***"); + final BufferedReader reader = new BufferedReader(new StringReader(text)); + final InputFile file = StreamInput.forReader(path, reader); + final SpecificationListBuilder specItemBuilder = SpecificationListBuilder.create(); + final Importer importer = importerFactory.createImporter(file, specItemBuilder); + importer.runImport(); + return specItemBuilder.build(); + } +} diff --git a/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/importer/lightweightmarkup/AbstractLightWeightMarkupImporterTest.java b/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/importer/lightweightmarkup/AbstractLightWeightMarkupImporterTest.java new file mode 100644 index 000000000..7d53d70c0 --- /dev/null +++ b/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/importer/lightweightmarkup/AbstractLightWeightMarkupImporterTest.java @@ -0,0 +1,532 @@ +package org.itsallcode.openfasttrace.testutil.importer.lightweightmarkup; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.itsallcode.matcher.auto.AutoMatcher.contains; +import static org.itsallcode.openfasttrace.testutil.core.ItemBuilderFactory.item; +import static org.itsallcode.openfasttrace.testutil.importer.ImportAssertions.assertImportWithFactory; +import static org.itsallcode.openfasttrace.testutil.importer.ImportAssertions.runImporterOnText; + +import java.nio.file.Path; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import org.hamcrest.Matcher; +import org.itsallcode.openfasttrace.api.core.*; +import org.itsallcode.openfasttrace.api.importer.ImporterFactory; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +public abstract class AbstractLightWeightMarkupImporterTest +{ + private static final Path PATH = Path.of("/a/b/c.markdown"); + private static final String NL = System.lineSeparator(); + private static final Pattern TITLE_PLACEHOLDER = Pattern.compile("\\$\\{title\\(\"([^\"]+)\", (\\d+)\\)}"); + private final int titleLocationOffset; + + protected AbstractLightWeightMarkupImporterTest(final int titleLocationOffset) + { + this.titleLocationOffset = titleLocationOffset; + } + + protected abstract String formatTitle(final String title, int level); + + // [utest -> dsn~md.specification-item-id-format~3] + @CsvSource({ + "at~name~67, at, name, 67", + "longartifacttype~name.with.dots~123456789, longartifacttype, name.with.dots, 123456789", + "a~b1.c1.d~0, a, b1.c1.d, 0" + }) + @ParameterizedTest + void testRequirementIdDetected(final String markdownId, final String expectedArtifactType, + final String expectedName, final int expectedRevision) + { + assertImport(PATH, markdownId, contains(item() + .id(expectedArtifactType, expectedName, expectedRevision) + .location(PATH.toString(), 1) + .build())); + } + + protected void assertImport(final String path, final String input, + final Matcher> matcher) + { + assertImport(Path.of(path), input, matcher); + } + + protected void assertImport(final Path path, final String input, + final Matcher> matcher) + { + assertImportWithFactory(path, processTextInput(input), matcher, getImporterFactory()); + } + + private String processTextInput(final String input) + { + return TITLE_PLACEHOLDER.matcher(input) + .replaceAll(match -> formatTitle(match.group(1), Integer.parseInt(match.group(2)))); + } + + protected abstract ImporterFactory getImporterFactory(); + + // [utest -> dsn~md.requirement-references~1] + @Test + void testSpecificationItemReferenceDetected() + { + assertImport(PATH, """ + `req~item-a~2` + + Covers: + * `feat~item-b~1` + """, + contains(item() + .id("req", "item-a", 2) + .addCoveredId("feat", "item-b", 1) + .location(PATH.toString(), 1) + .build())); + } + + // [utest -> dsn~md.covers-list~1] + @Test + void testSpecificationItemCoversList() + { + assertImport(PATH, """ + req~covers-list~4 + Covers: + * `feat~item-a~1` + * `feat~item-b~2` + * `feat~item-c~3` + """, + contains(item() + .id("req", "covers-list", 4) + .addCoveredId("feat", "item-a", 1) + .addCoveredId("feat", "item-b", 2) + .addCoveredId("feat", "item-c", 3) + .location(PATH.toString(), 1) + .build())); + } + + // [utest -> dsn~md.depends-list~1] + @Test + void testSpecificationItemDependsList() + { + assertImport(PATH, """ + req~depends-list~4 + Depends: + * `feat~item-a~1` + * `feat~item-b~2` + * `feat~item-c~3` + """, + contains(item() + .id("req", "depends-list", 4) + .addDependOnId("feat", "item-a", 1) + .addDependOnId("feat", "item-b", 2) + .addDependOnId("feat", "item-c", 3) + .location(PATH.toString(), 1) + .build())); + } + + // [utest -> dsn~md.needs-coverage-list-single-line~2] + @Test + void testSpecificationItemNeedsCoverageListCompact() + { + final Path path = Path.of("~/git/foo/bar.md"); + assertImport(path, """ + req~needs-coverage-list-single-line~4 + Needs: dsn, uman + """, + contains(item() + .id("req", "needs-coverage-list-single-line", 4) + .addNeedsArtifactType("dsn") + .addNeedsArtifactType("uman") + .location(path.toString(), 1) + .build())); + } + + static Stream tags() + { + return Stream.of( + Arguments.of("Tags: req , dsn ", List.of("req", "dsn")), + Arguments.of("Tags: req ", List.of("req")), + Arguments.of("Tags: req,dsn ", List.of("req", "dsn")), + Arguments.of("Tags: req ,dsn", List.of("req", "dsn")), + Arguments.of("Tags: req,dsn", List.of("req", "dsn")), + Arguments.of("Tags: req,\tdsn\n", List.of("req", "dsn")), + Arguments.of("Tags:req,dsn", List.of("req", "dsn")), + Arguments.of("Tags:\n* req\n* dsn", List.of("req", "dsn")), + Arguments.of("Tags:\n * req\n * dsn\n", List.of("req", "dsn")), + Arguments.of("Tags:\n* req \n\t* dsn ", List.of("req", "dsn")), + Arguments.of("Tags:\n* req\n* dsn", List.of("req", "dsn"))); + } + + @ParameterizedTest + @MethodSource("tags") + void testTags(final String mdContent, final List expected) + { + final List items = runImporterOnText(Path.of("irrelevant-filename"), + "`a~b~1`\n" + mdContent, + getImporterFactory()); + assertThat(items.get(0).getTags(), equalTo(expected)); + } + + // [utest -> dsn~md.needs-coverage-list~1] + @Test + void testSpecificationItemNeedsCoverageList() + { + assertImport("needs-list.md", """ + req~needs-coverage-list~4 + Needs: + * dsn + * uman + """, + contains(item() + .id("req", "needs-coverage-list", 4) + .addNeedsArtifactType("dsn") + .addNeedsArtifactType("uman") + .location("needs-list.md", 1) + .build())); + } + + // [utest -> dsn~md.artifact-forwarding-notation~1] + @CsvSource({ + "dsn-->impl:req~foobar~1, dsn, impl, req, foobar, 1", + " rsn-->sdk:req~foobar~2, rsn, sdk, req, foobar, 2", + "dsn-->impl : test~foobar~3 , dsn, impl, test, foobar, 3", + " rsn-->sdk : test~foobar~4 , rsn, sdk, test, foobar, 4", + "dsn-->impl\t: req~foobar~1, dsn, impl, req, foobar, 1", + "rsn-->sdk\t: \treq~foobar~2, rsn, sdk, req, foobar, 2", + "dsn -->impl : test~foobar~3\t , dsn, impl, test, foobar, 3", + "rsn -->sdk : test~foobar~4\t\t , rsn, sdk, test, foobar, 4" + }) + @ParameterizedTest + void testArtifactForwardingNotation(final String input, final String forwardedArtifactType, + final String targetArtifactType, final String originalArtifactType, + final String originalName, final int originalRevision) + { + assertImport("xyz", input, + contains(item() + .id(forwardedArtifactType, originalName, originalRevision) + .addCoveredId(originalArtifactType, originalName, originalRevision) + .addNeedsArtifactType(targetArtifactType) + .forwards(true) + .location("xyz", 1) + .build())); + } + + // [utest -> dsn~md.artifact-forwarding-notation~1] + @Test + void testForwardingAfterDepends() + { + assertImport("1.2.md", """ + dsn~foo~1 + Depends: + * req~foo~1 + dsn-->impl:req~bar~2 + """, + contains( + item() + .id("dsn", "foo", 1) + .location("1.2.md", 1) + .addDependOnId("req", "foo", 1) + .build(), + item() + .id("dsn", "bar", 2) + .addCoveredId("req", "bar", 2) + .addNeedsArtifactType("impl") + .location("1.2.md", 4) + .forwards(true) + .build())); + } + + // [utest -> dsn~md.artifact-forwarding-notation~1] + @Test + void testForwardingAfterTags() + { + assertImport("1.2.md", """ + dsn~foo~1 + Tags: vanilla, strawberry, mint + dsn-->impl:req~bar~2 + """, + contains( + item() + .id("dsn", "foo", 1) + .location("1.2.md", 1) + .addTag("vanilla") + .addTag("strawberry") + .addTag("mint") + .build(), + item() + .id("dsn", "bar", 2) + .addCoveredId("req", "bar", 2) + .addNeedsArtifactType("impl") + .location("1.2.md", 3) + .forwards(true) + .build())); + } + + // [utest -> dsn~md.artifact-forwarding-notation~1] + @Test + void testMultipleForwardsInARow() + { + assertImport("fwd.md", """ + ${title("A Collection of Different Forwards", 1)} + * `arch --> dsn : req~foo~1` + * arch -->dsn : req~bar~2 with a comment + dsn-->impl : req~zoo~3 + """, + contains( + item() + .id("arch", "foo", 1).addCoveredId("req", "foo", 1) + .addNeedsArtifactType("dsn") + .forwards(true) + .location("fwd.md", 2 + titleLocationOffset) + .build(), + item() + .id("arch", "bar", 2).addCoveredId("req", "bar", 2) + .addNeedsArtifactType("dsn") + .forwards(true) + .location("fwd.md", 3 + titleLocationOffset) + .build(), + item() + .id("dsn", "zoo", 3).addCoveredId("req", "zoo", 3) + .addNeedsArtifactType("impl") + .forwards(true) + .location("fwd.md", 4 + titleLocationOffset) + .build())); + } + + // [utest -> dsn~md.artifact-forwarding-notation~1] + @Test + void testArtifactForwardingAfterARegularSpecificationItem() + { + assertImport("üöä", """ + art~name~9876 + + ${title("Forwards", 1)} + a-->b:c~d~5 + """, + contains( + item() + .id("art", "name", 9876) + .location("üöä", 1) + .build(), + item() + .id("a", "d", 5) + .addCoveredId("c", "d", 5) + .addNeedsArtifactType("b") + .location("üöä", 4 + titleLocationOffset) + .forwards(true) + .build())); + } + + // [utest -> dsn~md.artifact-forwarding-notation~1] + @Test + void testForwardingAfterCovers() + { + assertImport("1.2.md", """ + dsn~foo~1 + Covers: + * req~foo~1 + dsn-->impl:req~bar~2 + """, + contains( + item() + .id("dsn", "foo", 1) + .location("1.2.md", 1) + .addCoveredId("req", "foo", 1) + .build(), + item() + .id("dsn", "bar", 2) + .addCoveredId("req", "bar", 2) + .addNeedsArtifactType("impl") + .location("1.2.md", 4) + .forwards(true) + .build())); + } + + // [utest -> dsn~md.artifact-forwarding-notation~1] + @Test + void testForwardingAfterNeeds() + { + assertImport("1.2.md", """ + dsn~foo~1 + Needs: impl + dsn-->impl:req~bar~2 + """, + contains( + item() + .id("dsn", "foo", 1) + .location("1.2.md", 1) + .addNeedsArtifactType("impl") + .build(), + item() + .id("dsn", "bar", 2) + .addCoveredId("req", "bar", 2) + .addNeedsArtifactType("impl") + .location("1.2.md", 3) + .forwards(true) + .build())); + } + + @Test + void testComplexRequirement() + { + assertImport("file name", """ + ${title("Requirement Title", 1)} + `type~id~1` + Description + + More description + + Rationale: + Rationale + More rationale + + Covers: + + * impl~foo1~1 + + [Link to baz2](#impl~baz2~2) + + Depends: + + + configuration~blubb.blah.blah~4711 + - db~blah.blubb~42 + + Comment: + + Comment + More comment + + Needs: artA , artB + """, + contains(item().id(SpecificationItemId.parseId("type~id~1")).title("Requirement Title") + .comment("Comment" + NL + "More comment") + .description("Description" + NL + NL + "More description") + .rationale("Rationale" + NL + "More rationale") + .addNeedsArtifactType("artA").addNeedsArtifactType("artB") + .addCoveredId(SpecificationItemId.parseId("impl~foo1~1")) + .addCoveredId(SpecificationItemId.parseId("impl~baz2~2")) + .addDependOnId(SpecificationItemId + .parseId("configuration~blubb.blah.blah~4711")) + .addDependOnId(SpecificationItemId.parseId("db~blah.blubb~42")) + .location("file name", 2 + titleLocationOffset) + .build())); + } + + @Test + void testTwoConsecutiveSpecificationItems() + { + assertImport("file1.md", """ + dsn~foo~1 + First description + + Comment: + + First comment + + dsn~bar~2 + Second description + + Rationale: + Second rationale + """, + contains( + item() + .id("dsn", "foo", 1) + .description("First description") + .comment("First comment") + .location("file1.md", 1) + .build(), + item() + .id("dsn", "bar", 2) + .description("Second description") + .rationale("Second rationale") + .location("file1.md", 8) + .build())); + } + + static Stream needsCoverage() + { + return Stream.of( + Arguments.of("Needs: req , dsn ", List.of("req", "dsn")), + Arguments.of("Needs: req ", List.of("req")), + Arguments.of("Needs: req,dsn ", List.of("req", "dsn")), + Arguments.of("Needs: req ,dsn", List.of("req", "dsn")), + Arguments.of("Needs: req,dsn", List.of("req", "dsn")), + Arguments.of("Needs: req,\tdsn\n", List.of("req", "dsn")), + Arguments.of("Needs:req,dsn", List.of("req", "dsn")), + Arguments.of("Needs:\n* req\n* dsn", List.of("req", "dsn")), + Arguments.of("Needs:\n * req\n * dsn", List.of("req", "dsn")), + Arguments.of("Needs:\n* req \n\t* dsn ", List.of("req", "dsn")), + Arguments.of("Needs:\n* req\n* dsn", List.of("req", "dsn"))); + } + + @ParameterizedTest + @MethodSource("needsCoverage") + void testNeedsCoverage(final String mdContent, final List expected) + { + final List items = runImporterOnText(Path.of("irrelevant-filename"), + "`a~b~1`\n" + mdContent, + getImporterFactory()); + assertThat(items.get(0).getNeedsArtifactTypes(), equalTo(expected)); + } + + @Test + void testItemStatsRecognized() + { + assertImport("status.MD", """ + `arch~status-enum~1` + Status: draft + """, + contains(item() + .id("arch", "status-enum", 1) + .status(ItemStatus.DRAFT) + .location("status.MD", 1) + .build())); + } + + // [impl -> dsn~md.specification-item-id-format~3] with UTF-8 + @Test + void testItemIdSupportsUTF8Characaters() + { + assertImport("umlauts", + """ + ${title("Die Implementierung muss den Zustand einzelner Zellen ändern", 3)} + `req~zellzustandsänderung~1 + Ermöglicht die Aktualisierung des Zustands von lebenden und toten Zellen in jeder Generation. + Needs: arch + """, + contains(item() + .id(SpecificationItemId.createId("req", "zellzustandsänderung", 1)) + .title("Die Implementierung muss den Zustand einzelner Zellen ändern") + .description("Ermöglicht die Aktualisierung des Zustands von lebenden und toten Zellen" + + " in jeder Generation.") + .location("umlauts", 2 + titleLocationOffset) + .addNeedsArtifactType("arch") + .build())); + } + + @Test + void testHeaderBelongsToNextItem() + { + assertImport("file", """ + ${title("Item 1", 1)} + `req~item1~1 + Item 1 description + + ${title("Item 2", 1)} + `req~item2~1 + Item 2 description + """, + contains(item().id(SpecificationItemId.createId("req", "item1", 1)) + .title("Item 1") + .description("Item 1 description") + .location("file", 2 + titleLocationOffset) + .build(), + item().id(SpecificationItemId.createId("req", "item2", 1)) + .title("Item 2") + .description("Item 2 description") + .location("file", 6 + (2 * titleLocationOffset)) + .build())); + } +} diff --git a/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/log/ShortClassNameFormatter.java b/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/log/ShortClassNameFormatter.java new file mode 100644 index 000000000..0df3b81d7 --- /dev/null +++ b/testutil/src/main/java/org/itsallcode/openfasttrace/testutil/log/ShortClassNameFormatter.java @@ -0,0 +1,85 @@ +package org.itsallcode.openfasttrace.testutil.log; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +/** + * This {@code java.util.logging} {@link Formatter} is based on + * {@link java.util.logging.SimpleFormatter}. It uses a fixed, single-line + * format and shortens the class name of the logger to the last part of the + * fully qualified class name. + *

+ * Configure the formatter in {@code logging.properties} like this: + * + *

+ * java.util.logging.ConsoleHandler.formatter = org.itsallcode.openfasttrace.testutil.log.ShortClassNameFormatter
+ * 
+ */ +public final class ShortClassNameFormatter extends Formatter +{ + private static final String FORMAT = "%1$tF %1$tT [%4$s] %2$s - %5$s %6$s%n"; + + public ShortClassNameFormatter() + { + // Suppress warning "class ... declares no explicit constructors, + // thereby exposing a default constructor" + } + + @Override + public String format(final LogRecord logRecord) + { + return String.format(FORMAT, + getTimestamp(logRecord), + getSource(logRecord), + logRecord.getLoggerName(), + logRecord.getLevel().toString(), + formatMessage(logRecord), + formatThrowable(logRecord)); + } + + private static String getSource(final LogRecord logRecord) + { + if (logRecord.getSourceClassName() != null) + { + String source = shortenClassName(logRecord.getSourceClassName()); + if (logRecord.getSourceMethodName() != null) + { + source += " " + logRecord.getSourceMethodName(); + } + return source; + } + else + { + return logRecord.getLoggerName(); + } + } + + private static ZonedDateTime getTimestamp(final LogRecord logRecord) + { + return ZonedDateTime.ofInstant(logRecord.getInstant(), ZoneId.systemDefault()); + } + + private static String formatThrowable(final LogRecord logRecord) + { + if (logRecord.getThrown() == null) + { + return ""; + } + final StringWriter sw = new StringWriter(); + final PrintWriter pw = new PrintWriter(sw); + pw.println(); + logRecord.getThrown().printStackTrace(pw); + pw.close(); + return sw.toString(); + } + + private static String shortenClassName(final String className) + { + final String[] parts = className.split("\\."); + return parts[parts.length - 1]; + } +}