Skip to content

Commit

Permalink
Allow qualifiers in changelog versions
Browse files Browse the repository at this point in the history
Affects: #24
  • Loading branch information
io7m committed May 3, 2024
1 parent 9a4816a commit 4f65a26
Show file tree
Hide file tree
Showing 12 changed files with 485 additions and 171 deletions.
7 changes: 6 additions & 1 deletion README-CHANGES.xml
Expand Up @@ -34,9 +34,14 @@
<c:change date="2021-01-12T00:00:00+00:00" summary="Improved documentation"/>
</c:changes>
</c:release>
<c:release date="2024-04-28T20:11:40+00:00" is-open="true" ticket-system="com.io7m.github.changelog" version="5.0.0">
<c:release date="2024-05-03T18:52:30+00:00" is-open="true" ticket-system="com.io7m.github.changelog" version="5.0.0">
<c:changes>
<c:change compatible="false" date="2024-04-28T00:00:00+00:00" summary="Require JDK 17"/>
<c:change date="2024-05-03T18:52:30+00:00" summary="Allow qualifiers in version numbers.">
<c:tickets>
<c:ticket id="24"/>
</c:tickets>
</c:change>
</c:changes>
</c:release>
<c:release date="2017-01-10T00:00:00+00:00" is-open="false" ticket-system="com.io7m.github.changelog" version="3.0.3">
Expand Down
Expand Up @@ -20,6 +20,7 @@
import com.beust.jcommander.Parameters;
import com.io7m.changelog.core.CChangelog;
import com.io7m.changelog.core.CRelease;
import com.io7m.changelog.core.CVersion;
import com.io7m.changelog.core.CVersions;
import com.io7m.changelog.parser.api.CParseErrorHandlers;
import com.io7m.changelog.xml.api.CXMLChangelogParserProviderType;
Expand Down Expand Up @@ -100,9 +101,12 @@ public Status executeActual()
final var changelog =
parsers.parse(this.path, CParseErrorHandlers.loggingHandler(LOG));

final var targetVersionOpt =
Optional.ofNullable(this.versionText)
.map(CVersions::parse);
final Optional<CVersion> targetVersionOpt;
if (this.versionText != null) {
targetVersionOpt = Optional.of(CVersions.parse(this.versionText));
} else {
targetVersionOpt = Optional.empty();
}

final var targetReleaseOpt =
changelog.findTargetReleaseOrLatestOpen(targetVersionOpt);
Expand Down
@@ -0,0 +1,153 @@
/*
* Copyright © 2024 Mark Raynsford <code@io7m.com> https://www.io7m.com
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

package com.io7m.changelog.core;

import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;

import static java.lang.Integer.compareUnsigned;
import static java.lang.Integer.parseUnsignedInt;

/**
* A version number qualifier.
*
* @param text The qualifier text
*/

public record CVersionQualifier(
String text)
implements Comparable<CVersionQualifier>
{
private static final Pattern VALID_QUALIFIER =
Pattern.compile("[A-Za-z0-9\\-]+(\\.[A-Za-z0-9\\-]+)*");

private static final Pattern IS_NUMERIC =
Pattern.compile("[0-9]+");

/**
* A version number qualifier.
*
* @param text The qualifier text
*/

public CVersionQualifier
{
Objects.requireNonNull(text, "text");

final var matcher = VALID_QUALIFIER.matcher(text);
if (!matcher.matches()) {
throw new IllegalArgumentException(
"Qualifier '%s' must match the pattern '%s'"
.formatted(text, VALID_QUALIFIER)
);
}
}

/**
* @return {@code true} if this version qualifier denotes a snapshot version
*/

public boolean isSnapshot()
{
return Objects.equals(this.text, "SNAPSHOT");
}

@Override
public int compareTo(
final CVersionQualifier other)
{
if (this.text.equals(other.text)) {
return 0;
}

final var identifiersA =
List.of(this.text.split("\\."));
final var identifiersB =
List.of(other.text.split("\\."));

/*
* 1. Identifiers consisting of only digits are compared numerically.
*
* 2. Identifiers with letters or hyphens are compared lexically in ASCII
* sort order.
*
* 3. Numeric identifiers always have lower precedence than non-numeric
* identifiers.
*
* 4. A larger set of pre-release fields has a higher precedence than a
* smaller set, if all the preceding identifiers are equal.
*/

final var sizeA = identifiersA.size();
final var sizeB = identifiersB.size();
final var smallest = Math.min(sizeA, sizeB);

for (int index = 0; index < smallest; ++index) {
final var r =
compareIdentifier(identifiersA.get(index), identifiersB.get(index));
if (r != 0) {
return r;
}
}

return (sizeA < sizeB) ? -1 : 1;
}

private static int compareIdentifier(
final String identifierA,
final String identifierB)
{
final var numericA =
IS_NUMERIC.matcher(identifierA).matches();
final var numericB =
IS_NUMERIC.matcher(identifierB).matches();

/*
* Identifiers consisting of only digits are compared numerically.
*/

if (numericA && numericB) {
return compareUnsigned(
parseUnsignedInt(identifierA),
parseUnsignedInt(identifierB)
);
}

/*
* Identifiers with letters or hyphens are compared lexically in ASCII
* sort order.
*/

if (!numericA && !numericB) {
return identifierA.compareTo(identifierB);
}

/*
* Numeric identifiers always have lower precedence than non-numeric
* identifiers.
*/

return numericA ? -1 : 1;
}

@Override
public String toString()
{
return this.text;
}
}
Expand Up @@ -24,6 +24,7 @@
import java.util.Comparator;
import java.util.Formattable;
import java.util.Formatter;
import java.util.Optional;

/**
* A version number.
Expand Down Expand Up @@ -54,13 +55,22 @@ public interface CVersionType extends Comparable<CVersionType>, Formattable
@Value.Parameter
BigInteger patch();

/**
* @return The version qualifier
*/

Optional<CVersionQualifier> qualifier();

@Override
default int compareTo(
final CVersionType other)
{
return Comparator.comparing(CVersionType::major)
.thenComparing(CVersionType::minor)
.thenComparing(CVersionType::patch)
.thenComparing(
CVersionType::qualifier,
QVersionQualifiers.COMPARE_QUALIFIER)
.compare(this, other);
}

Expand Down Expand Up @@ -89,6 +99,16 @@ default void formatTo(
final int width,
final int precision)
{
formatter.format("%s.%s.%s", this.major(), this.minor(), this.patch());
if (this.qualifier().isPresent()) {
formatter.format(
"%s.%s.%s-%s",
this.major(),
this.minor(),
this.patch(),
this.qualifier().get()
);
} else {
formatter.format("%s.%s.%s", this.major(), this.minor(), this.patch());
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright © 2016 <code@io7m.com> http://io7m.com
* Copyright © 2022 Mark Raynsford <code@io7m.com> https://www.io7m.com
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
Expand All @@ -14,52 +14,71 @@
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

package com.io7m.changelog.core;

import com.io7m.junreachable.UnreachableCodeException;
package com.io7m.changelog.core;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;

/**
* Functions for parsing and processing versions.
* A parser of version numbers.
*/

public final class CVersions
{
private static Pattern VALID_VERSION =
Pattern.compile("(\\p{Nd}+)\\.(\\p{Nd}+)\\.(\\p{Nd}+)");
private static final Pattern VERSION_TEXT =
Pattern.compile("([0-9]+)\\.([0-9]+)\\.([0-9]+)(-(.+))?");

private CVersions()
{
throw new UnreachableCodeException();

}

/**
* Attempt to parse a version number.
* Parse a version number.
*
* @param version The version string
* @param text The version text
*
* @return A new version number
* @return The parsed version
*
* @throws IOException On errors
*/

public static CVersion parse(
final String version)
final String text)
throws IOException
{
Objects.requireNonNull(version, "Version");

final var matcher = VALID_VERSION.matcher(version);
final var matcher = VERSION_TEXT.matcher(text);
if (matcher.matches()) {
final var major = new BigInteger(matcher.group(1));
final var minor = new BigInteger(matcher.group(2));
final var patch = new BigInteger(matcher.group(3));
return CVersion.of(major, minor, patch);
try {
final var qualifierText = matcher.group(5);
final Optional<CVersionQualifier> qualifier;
if (qualifierText != null) {
qualifier = Optional.of(new CVersionQualifier(qualifierText));
} else {
qualifier = Optional.empty();
}

return CVersion.builder()
.setMajor(new BigInteger(matcher.group(1)))
.setMinor(new BigInteger(matcher.group(2)))
.setPatch(new BigInteger(matcher.group(3)))
.setQualifier(qualifier)
.build();
} catch (final Exception e) {
throw new IOException(
"Version text '%s' cannot be parsed: %s"
.formatted(text, e.getMessage()),
e
);
}
}

throw new IllegalArgumentException(String.format(
"Expected a version number matching '%s'",
VALID_VERSION)
throw new IOException(
"Version text '%s' must match the pattern '%s'"
.formatted(text, VERSION_TEXT)
);
}
}
@@ -0,0 +1,51 @@
/*
* Copyright © 2024 Mark Raynsford <code@io7m.com> https://www.io7m.com
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/


package com.io7m.changelog.core;

import java.util.Comparator;
import java.util.Objects;
import java.util.Optional;

/**
* Functions over version qualifiers.
*/

final class QVersionQualifiers
{
private QVersionQualifiers()
{

}

static final Comparator<Optional<CVersionQualifier>> COMPARE_QUALIFIER =
(x, y) -> {
if (Objects.equals(x, y)) {
return 0;
}
if (x.isEmpty()) {
return 1;
}
if (y.isEmpty()) {
return -1;
}

final var xx = x.get();
final var yy = y.get();
return xx.compareTo(yy);
};
}

0 comments on commit 4f65a26

Please sign in to comment.