Skip to content

Commit

Permalink
Add versioning extension point
Browse files Browse the repository at this point in the history
  • Loading branch information
charphi committed Apr 5, 2024
1 parent c1a0504 commit 82dc591
Show file tree
Hide file tree
Showing 17 changed files with 161 additions and 73 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Improve list command output [#231](https://github.com/nbbrd/heylogs/issues/231)
- Add error severity to failures [#17](https://github.com/nbbrd/heylogs/issues/17)
- Add json formatting [#118](https://github.com/nbbrd/heylogs/issues/118)
- Add versioning extension point [#235](https://github.com/nbbrd/heylogs/issues/235)

### Changed

Expand Down
6 changes: 3 additions & 3 deletions heylogs-api/src/main/java/internal/heylogs/StylishFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ private List<String> getStatusBody(Summary summary) {
return asList(
String.format(ROOT, "Found %d releases", summary.getReleaseCount()),
String.format(ROOT, "Ranging from %s to %s", summary.getTimeRange().getFrom(), summary.getTimeRange().getTo()),
summary.isCompatibleWithSemver()
? "Compatible with Semantic Versioning" + summary.getSemverDetails()
: "Not compatible with Semantic Versioning",
summary.getCompatibilities().isEmpty()
? "Not compatible with known versioning"
: "Compatible with " + String.join(", ", summary.getCompatibilities()),
summary.isHasUnreleasedSection() ? "Has an unreleased version" : "Has no unreleased version"
);
}
Expand Down
27 changes: 27 additions & 0 deletions heylogs-api/src/main/java/internal/heylogs/semver/SemVer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package internal.heylogs.semver;

import lombok.NonNull;
import nbbrd.design.DirectImpl;
import nbbrd.heylogs.spi.Versioning;
import nbbrd.service.ServiceProvider;
import org.semver4j.Semver;

@DirectImpl
@ServiceProvider
public final class SemVer implements Versioning {

@Override
public @NonNull String getVersioningId() {
return "semver";
}

@Override
public @NonNull String getVersioningName() {
return "Semantic Versioning";
}

@Override
public boolean isValidVersion(@NonNull CharSequence text) {
return Semver.isValid(text.toString());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package internal.heylogs;
package internal.heylogs.semver;

import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.util.ast.Node;
Expand All @@ -12,7 +12,7 @@
import org.semver4j.Semver;

@ServiceProvider
public final class SemverRule implements Rule {
public final class SemVerRule implements Rule {

@Override
public @NonNull String getRuleId() {
Expand Down
66 changes: 39 additions & 27 deletions heylogs-api/src/main/java/nbbrd/heylogs/Heylogs.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,18 @@
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.ast.Node;
import lombok.NonNull;
import nbbrd.design.MightBePromoted;
import nbbrd.design.StaticFactoryMethod;
import nbbrd.heylogs.spi.*;
import org.semver4j.Semver;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static nbbrd.heylogs.TimeRange.toTimeRange;
import static nbbrd.heylogs.Util.illegalArgumentToNull;
Expand All @@ -33,6 +30,7 @@ public class Heylogs {
.builder()
.rules(RuleLoader.load())
.formats(FormatLoader.load())
.versionings(VersioningLoader.load())
.build();
}

Expand All @@ -44,15 +42,22 @@ public class Heylogs {
@lombok.Singular
List<Format> formats;

@NonNull
@lombok.Singular
List<Versioning> versionings;

public @NonNull List<Problem> validate(@NonNull Document doc) {
return Stream.concat(Stream.of(doc), Nodes.of(Node.class).descendants(doc))
return concat(Stream.of(doc), Nodes.of(Node.class).descendants(doc))
.flatMap(node -> rules.stream().map(rule -> getProblemOrNull(node, rule)).filter(Objects::nonNull))
.collect(toList());
}

public @NonNull List<Resource> getResources() {
return Stream
.concat(rules.stream().map(Heylogs::asResource), formats.stream().map(Heylogs::asResource))
return concat(
rules.stream().map(Heylogs::asResource),
formats.stream().map(Heylogs::asResource),
versionings.stream().map(Heylogs::asResource)
)
.sorted(comparing(Resource::getType).thenComparing(Resource::getCategory).thenComparing(Resource::getId))
.collect(toList());
}
Expand All @@ -77,6 +82,16 @@ private static Resource asResource(Format format) {
.build();
}

private static Resource asResource(Versioning versioning) {
return Resource
.builder()
.type("versioning")
.category("main")
.id(versioning.getVersioningId())
.name(versioning.getVersioningName())
.build();
}

private static Problem getProblemOrNull(Node node, Rule rule) {
RuleIssue ruleIssueOrNull = rule.getRuleIssueOrNull(node);
return ruleIssueOrNull != null ? Problem.builder().rule(rule).issue(ruleIssueOrNull).build() : null;
Expand All @@ -102,35 +117,22 @@ public void formatResources(@NonNull String formatId, @NonNull Appendable append
.filter(Objects::nonNull)
.collect(Collectors.partitioningBy(Version::isUnreleased));

boolean compatibleWithSemver = isCompatibleWithSemver(versionByType.get(false));
List<String> compatibilities = getCompatibilities(versionByType.get(false));

return Summary
.builder()
.releaseCount(versionByType.get(false).size())
.timeRange(versionByType.get(false).stream().map(Version::getDate).collect(toTimeRange()).orElse(TimeRange.ALL))
.compatibleWithSemver(compatibleWithSemver)
.semverDetails(compatibleWithSemver ? getSemverDetails(versionByType.get(false)) : "")
.compatibilities(compatibilities)
.hasUnreleasedSection(versionByType.containsKey(true))
.build();
}

private static boolean isCompatibleWithSemver(List<Version> releases) {
return releases.stream().map(Version::getRef).allMatch(Semver::isValid);
}

private static String getSemverDetails(List<Version> releases) {
List<Semver> semvers = releases.stream().map(Version::getRef).map(Semver::parse).collect(toList());

TreeMap<Semver.VersionDiff, List<Semver.VersionDiff>> diffs = IntStream.range(1, semvers.size())
.mapToObj(i -> semvers.get(i).diff(semvers.get(i - 1)))
.collect(groupingBy((Semver.VersionDiff o) -> o, TreeMap::new, toList()));

return diffs
.descendingMap()
.entrySet()
.stream()
.map(entry -> entry.getValue().size() + " " + entry.getKey().toString())
.collect(Collectors.joining(", ", " (", ")"));
private List<String> getCompatibilities(List<Version> releases) {
return versionings.stream()
.filter(versioning -> releases.stream().allMatch(release -> versioning.isValidVersion(release.getRef())))
.map(Versioning::getVersioningName)
.collect(toList());
}

private Format getFormatById(String formatId) throws IOException {
Expand All @@ -141,4 +143,14 @@ private Format getFormatById(String formatId) throws IOException {
}

public static final String FIRST_FORMAT_AVAILABLE = "";

@MightBePromoted
@SafeVarargs
private static <T> Stream<T> concat(Stream<T> first, Stream<T>... rest) {
Stream<T> result = first;
for (Stream<T> next : rest) {
result = Stream.concat(result, next);
}
return result;
}
}
9 changes: 4 additions & 5 deletions heylogs-api/src/main/java/nbbrd/heylogs/Summary.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package nbbrd.heylogs;

import java.util.List;

@lombok.Value
@lombok.Builder
public class Summary {
Expand All @@ -10,11 +12,8 @@ public class Summary {
@lombok.Builder.Default
TimeRange timeRange = TimeRange.ALL;

@lombok.Builder.Default
boolean compatibleWithSemver = false;

@lombok.Builder.Default
String semverDetails = "";
@lombok.Singular
List<String> compatibilities;

@lombok.Builder.Default
boolean hasUnreleasedSection = false;
Expand Down
19 changes: 19 additions & 0 deletions heylogs-api/src/main/java/nbbrd/heylogs/spi/Versioning.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package nbbrd.heylogs.spi;

import lombok.NonNull;
import nbbrd.service.Quantifier;
import nbbrd.service.ServiceDefinition;
import nbbrd.service.ServiceId;

@ServiceDefinition(
quantifier = Quantifier.MULTIPLE
)
public interface Versioning {

@ServiceId(pattern = ServiceId.KEBAB_CASE)
@NonNull String getVersioningId();

@NonNull String getVersioningName();

boolean isValidVersion(@NonNull CharSequence text);
}
3 changes: 1 addition & 2 deletions heylogs-api/src/test/java/_test/Sample.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,9 @@ public static String asText(Heading heading) {
public static final Summary SUMMARY_1 = Summary.builder().build();
public static final Summary SUMMARY_2 = Summary
.builder()
.compatibleWithSemver(true)
.compatibility("Strange Versioning")
.releaseCount(3)
.hasUnreleasedSection(true)
.semverDetails("XXX")
.timeRange(TimeRange.of(LocalDate.of(2010, 1, 1), LocalDate.of(2011, 1, 1)))
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ public void testFormatStatus() {
assertThat(writing(appendable -> x.formatStatus(appendable, singletonList(SCAN2))))
.isEqualToNormalizingNewlines(
"source2\n"
+ " Found 3 releases \n"
+ " Ranging from 2010-01-01 to 2011-01-01 \n"
+ " Compatible with Semantic VersioningXXX\n"
+ " Has an unreleased version \n"
+ " Found 3 releases \n"
+ " Ranging from 2010-01-01 to 2011-01-01\n"
+ " Compatible with Strange Versioning \n"
+ " Has an unreleased version \n"
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package internal.heylogs;
package internal.heylogs.semver;

import com.vladsch.flexmark.ast.Heading;
import com.vladsch.flexmark.util.ast.Node;
Expand All @@ -12,17 +12,17 @@
import static nbbrd.heylogs.Nodes.of;
import static org.assertj.core.api.Assertions.assertThat;

public class SemverRuleTest {
public class SemVerRuleTest {

@Test
public void testIdPattern() {
assertThat(new SemverRule().getRuleId())
assertThat(new SemVerRule().getRuleId())
.matches(ServiceId.KEBAB_CASE);
}

@Test
public void testSample() {
SemverRule x = new SemverRule();
SemVerRule x = new SemVerRule();

assertThat(of(Node.class).descendants(using("/Main.md")))
.map(x::getRuleIssueOrNull)
Expand All @@ -32,7 +32,7 @@ public void testSample() {

@Test
public void testValidateSemVer() {
SemverRule x = new SemverRule();
SemVerRule x = new SemVerRule();

assertThat(of(Heading.class).descendants(using("/Main.md")))
.map(x::validateSemVer)
Expand Down
28 changes: 28 additions & 0 deletions heylogs-api/src/test/java/internal/heylogs/semver/SemVerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package internal.heylogs.semver;

import nbbrd.heylogs.spi.VersioningLoader;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class SemVerTest {

@Test
void getVersioningId() {
assertThat(new SemVer().getVersioningId())
.matches(VersioningLoader.ID_PATTERN);
}

@Test
void getVersioningName() {
assertThat(new SemVer().getVersioningName())
.isNotBlank();
}

@Test
void isValidVersion() {
SemVer x = new SemVer();
assertThat(x.isValidVersion("1.1.0")).isTrue();
assertThat(x.isValidVersion(".1.0")).isFalse();
}
}
Loading

0 comments on commit 82dc591

Please sign in to comment.