Initial commit

This commit is contained in:
Peter Niederwieser
2016-01-19 14:51:19 +01:00
committed by Dan Chao
commit ecad035dca
2972 changed files with 211653 additions and 0 deletions

206
stdlib/semver.pkl Normal file
View File

@@ -0,0 +1,206 @@
//===----------------------------------------------------------------------===//
// Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===----------------------------------------------------------------------===//
/// Parsing, comparison, and manipulation of [semantic version](https://semver.org/spec/v2.0.0.html) numbers.
@ModuleInfo { minPklVersion = "0.25.0" }
module pkl.semver
/// Tells whether [version] is a valid semantic version number.
function isValid(version: String) = parseOrNull(version) != null
/// Parses [version] as a semantic version number.
///
/// Throws if [version] is not a valid semantic version number.
///
/// Examples:
/// ```
/// semver.Version("1.2.3")
/// semver.Version("1.2.3-alpha")
/// semver.Version("1.2.3+456")
/// semver.Version("1.2.3-alpha+456")
/// ```
function Version(version: String): Version =
parseOrNull(version) ?? throw("`\(version)` is not a valid semantic version number.")
/// Parses [version] as a semantic version number.
///
/// Returns [null] if [version] is not a valid semantic version number.
///
/// Facts:
/// ```
/// semver.parseOrNull("1.2.3") == semver.Version("1.2.3")
/// semver.parseOrNull("1.2.3-alpha+456") == semver.Version("1.2.3-alpha+456")
/// semver.parseOrNull("1") == null
/// ```
function parseOrNull(version: String): Version? =
let (groups = versionRegex.matchEntire(version)?.groups)
if (groups == null) null
else
new Version {
major = groups[1].value.toInt()
minor = groups[2].value.toInt()
patch = groups[3].value.toInt()
preRelease = groups[4]?.value
build = groups[5]?.value
}
/// A version comparator for use with methods such as [List.minWith()].
comparator: (Version, Version) -> Boolean = (v1: Version, v2: Version) -> v1.isLessThan(v2)
/// A [semantic version](https://semver.org/spec/v2.0.0.html).
///
/// To test if two versions are equal according to semantic versioning rules, use [equals()] instead of `==`.
class Version {
/// Major version zero (0.y.z) is for initial development.
/// Anything MAY change at any time.
/// The public API SHOULD NOT be considered stable.
///
/// Version 1.0.0 defines the public API.
/// The way in which the version number is incremented after this release is dependent on this public API and how it changes.
///
/// Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced to the public API.
/// It MAY also include minor and patch level changes.
/// Patch and minor version MUST be reset to 0 when major version is incremented.
major: UInt
/// Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is introduced to the public API.
/// It MUST be incremented if any public API functionality is marked as deprecated.
/// It MAY be incremented if substantial new functionality or improvements are introduced within the private code.
/// It MAY include patch level changes.
/// Patch version MUST be reset to 0 when minor version is incremented.
minor: UInt
/// Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible bug fixes are introduced.
/// A bug fix is defined as an internal change that fixes incorrect behavior.
patch: UInt
/// A pre-release version MAY be denoted by appending a hyphen and a series of dot separated identifiers immediately following the patch version.
/// Identifiers MUST comprise only ASCII alphanumerics and hyphens `[0-9A-Za-z-]`.
/// Identifiers MUST NOT be empty. Numeric identifiers MUST NOT include leading zeroes.
/// Pre-release versions have a lower precedence than the associated normal version.
/// A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version.
///
/// Examples:
/// - `"1.0.0-alpha"`
/// - `"1.0.0-alpha.1"`
/// - `"1.0.0-0.3.7"`
/// - `"1.0.0-x.7.z.92"`
/// - `"1.0.0-x-y-z."`
preRelease: String(matches(Regex(#"(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*"#)))?
hidden fixed preReleaseIdentifiers: List<Int|String> =
if (preRelease == null) List() else preRelease.split(".").map((it) -> it.toIntOrNull() ?? it)
/// Build metadata MAY be denoted by appending a plus sign and a series of dot separated identifiers immediately following the patch or pre-release version.
/// Identifiers MUST comprise only ASCII alphanumerics and hyphens `[0-9A-Za-z-]`.
/// Identifiers MUST NOT be empty.
/// Build metadata MUST be ignored when determining version precedence.
/// Thus two versions that differ only in the build metadata, have the same precedence.
///
/// Examples:
/// - `"1.0.0-alpha+001"
/// - `"1.0.0+20130313144700"`
/// - `"1.0.0-beta+exp.sha.5114f85"`
/// - `"1.0.0+21AF26D3—-117B344092BD"`
///
/// Note: Unlike `==`, [equals()] and comparison methods such as [isLessThan()] ignore [build].
build: String(matches(Regex(#"[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*"#)))?
hidden fixed buildIdentifiers: List<String> =
if (build == null) List() else build.split(".")
/// Tells whether this version is equal to [other] according to semantic versioning rules.
///
/// Facts:
/// ```
/// semver.Version("1.0.0").equals(semver.Version("1.0.0"))
/// !(semver.Version("1.0.0").equals(semver.Version("1.0.1")))
/// semver.Version("1.0.0-alpha+001").equals(semver.Version("1.0.0-alpha+999"))
/// ```
///
/// Note: `version1.equals(version2)` differs from `version1 == version2` in that it ignores [build].
function equals(other: Version): Boolean =
major == other.major &&
minor == other.minor &&
patch == other.patch &&
preRelease == other.preRelease
/// Tells whether this version is less than [other] according to semantic versioning rules.
///
/// Facts:
/// ```
/// semver.Version("1.0.0").isLessThan(semver.Version("2.0.0"))
/// semver.Version("2.0.0").isLessThan(semver.Version("2.1.0"))
/// semver.Version("2.1.0").isLessThan(semver.Version("2.1.1"))
///
/// semver.Version("1.0.0-alpha").isLessThan("1.0.0")
///
/// semver.Version("1.0.0-alpha").isLessThan(semver.Version("1.0.0-alpha.1"))
/// semver.Version("1.0.0-alpha.1").isLessThan(semver.Version("1.0.0-alpha.beta"))
/// semver.Version("1.0.0-alpha.beta").isLessThan(semver.Version("1.0.0-beta"))
/// semver.Version("1.0.0-beta").isLessThan(semver.Version("1.0.0-beta.2"))
/// semver.Version("1.0.0-beta.2").isLessThan(semver.Version("1.0.0-beta.11"))
/// semver.Version("1.0.0-beta.11").isLessThan(semver.Version("1.0.0-rc.1"))
/// semver.Version("1.0.0-rc.1").isLessThan(semver.Version("1.0.0"))
/// ```
function isLessThan(other: Version): Boolean =
major < other.major ||
minor < other.minor ||
patch < other.patch ||
isPreReleaseLessThan(other)
/// Tells whether this version is less than or equal to [other] according to semantic versioning rules.
function isLessThanOrEquals(other: Version): Boolean =
isLessThan(other) || equals(other)
/// Tells whether this version is greater than [other] according to semantic versioning rules.
function isGreaterThan(other: Version): Boolean =
other.isLessThan(this)
/// Tells whether this version is greater than or equal to [other] according to semantic versioning rules.
function isGreaterThanOrEquals(other: Version): Boolean =
other.isLessThanOrEquals(this)
/// A normal version number MUST take the form X.Y.Z where X, Y, and Z are non-negative integers, and MUST NOT contain leading zeroes.
function isNormal(): Boolean = preRelease == null && build == null
/// Tells if this version has a non-zero [major] and no [preRelease].
function isStable(): Boolean = major > 0 && preRelease == null
/// Strips [preRelease] and [build] from this version.
function toNormal(): Version = (this) { preRelease = null; build = null }
function toString() = "\(major).\(minor).\(patch)\(if (preRelease != null) "-\(preRelease)" else "")\(if (build != null) "+\(build)" else "")"
local function isPreReleaseLessThan(other: Version): Boolean =
if (preRelease == null) false
else if (other.preRelease == null) true
else if (preRelease == other.preRelease) false
else
let (result = preReleaseIdentifiers
.zip(other.preReleaseIdentifiers)
.fold(null, (result, next) ->
if (result != null) result
else
if (next.first == next.second) null
else if (next.first.getClass() == next.second.getClass()) next.first < next.second
else next.first is Int
)
) if (result != null) result else preReleaseIdentifiers.length < other.preReleaseIdentifiers.length
}
// https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
local versionRegex = Regex(#"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?"#)