aboutsummaryrefslogtreecommitdiff
path: root/src/install/semver.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/install/semver.zig')
-rw-r--r--src/install/semver.zig408
1 files changed, 227 insertions, 181 deletions
diff --git a/src/install/semver.zig b/src/install/semver.zig
index 44827b1a8..c92597eff 100644
--- a/src/install/semver.zig
+++ b/src/install/semver.zig
@@ -182,7 +182,7 @@ pub const String = extern struct {
} else {
const a = this.ptr();
const b = that.ptr();
- return strings.eql(this_buf[0..a.len], that_buf[0..b.len]);
+ return strings.eql(this_buf[a.off..][0..a.len], that_buf[b.off..][0..b.len]);
}
}
@@ -563,7 +563,7 @@ pub const Version = extern struct {
major: u32 = 0,
minor: u32 = 0,
patch: u32 = 0,
- tag: Tag = Tag{},
+ tag: Tag = .{},
// raw: RawType = RawType{},
/// Assumes that there is only one buffer for all the strings
@@ -576,7 +576,7 @@ pub const Version = extern struct {
}
pub fn cloneInto(this: Version, slice: []const u8, buf: *[]u8) Version {
- return Version{
+ return .{
.major = this.major,
.minor = this.minor,
.patch = this.patch,
@@ -589,7 +589,7 @@ pub const Version = extern struct {
}
pub fn fmt(this: Version, input: string) Formatter {
- return Formatter{ .version = this, .input = input };
+ return .{ .version = this, .input = input };
}
pub fn count(this: Version, buf: []const u8, comptime StringBuilder: type, builder: StringBuilder) void {
@@ -606,10 +606,38 @@ pub const Version = extern struct {
return that;
}
- const HashableVersion = extern struct { major: u32, minor: u32, patch: u32, pre: u64, build: u64 };
+ pub const Partial = struct {
+ major: ?u32 = null,
+ minor: ?u32 = null,
+ patch: ?u32 = null,
+ tag: Tag = .{},
+
+ pub fn fill(this: Partial) Version {
+ return .{
+ .major = this.major orelse 0,
+ .minor = this.minor orelse 0,
+ .patch = this.patch orelse 0,
+ .tag = this.tag,
+ };
+ }
+ };
+
+ const Hashable = extern struct {
+ major: u32,
+ minor: u32,
+ patch: u32,
+ pre: u64,
+ build: u64,
+ };
pub fn hash(this: Version) u64 {
- const hashable = HashableVersion{ .major = this.major, .minor = this.minor, .patch = this.patch, .pre = this.tag.pre.hash, .build = this.tag.build.hash };
+ const hashable = Hashable{
+ .major = this.major,
+ .minor = this.minor,
+ .patch = this.patch,
+ .pre = this.tag.pre.hash,
+ .build = this.tag.build.hash,
+ };
const bytes = std.mem.asBytes(&hashable);
return std.hash.Wyhash.hash(0, bytes);
}
@@ -620,15 +648,15 @@ pub const Version = extern struct {
pub fn format(formatter: Formatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
const self = formatter.version;
- try std.fmt.format(writer, "{d}.{d}.{d}", .{ self.major, self.minor, self.patch });
+ try std.fmt.format(writer, "{?d}.{?d}.{?d}", .{ self.major, self.minor, self.patch });
- if (self.tag.pre.len() > 0) {
+ if (!self.tag.pre.isEmpty()) {
const pre = self.tag.pre.slice(formatter.input);
try writer.writeAll("-");
try writer.writeAll(pre);
}
- if (self.tag.build.len() > 0) {
+ if (!self.tag.build.isEmpty()) {
const build = self.tag.build.slice(formatter.input);
try writer.writeAll("+");
try writer.writeAll(build);
@@ -661,11 +689,11 @@ pub const Version = extern struct {
if (lhs.patch < rhs.patch) return .lt;
if (lhs.patch > rhs.patch) return .gt;
- if (lhs.tag.hasPre() != rhs.tag.hasPre())
- return if (lhs.tag.hasPre()) .lt else .gt;
-
- if (lhs.tag.hasBuild() != rhs.tag.hasBuild())
- return if (lhs.tag.hasBuild()) .gt else .lt;
+ if (lhs.tag.hasPre()) {
+ if (!rhs.tag.hasPre()) return .lt;
+ } else {
+ if (rhs.tag.hasPre()) return .gt;
+ }
return .eq;
}
@@ -715,7 +743,7 @@ pub const Version = extern struct {
buf.* = buf.*[build_slice.len..];
}
- return Tag{
+ return .{
.pre = .{
.value = pre,
.hash = this.pre.hash,
@@ -736,7 +764,7 @@ pub const Version = extern struct {
}
pub fn eql(lhs: Tag, rhs: Tag) bool {
- return lhs.build.hash == rhs.build.hash and lhs.pre.hash == rhs.pre.hash;
+ return lhs.pre.hash == rhs.pre.hash;
}
pub const TagResult = struct {
@@ -859,9 +887,9 @@ pub const Version = extern struct {
};
pub const ParseResult = struct {
- wildcard: Query.Token.Wildcard = Query.Token.Wildcard.none,
+ wildcard: Query.Token.Wildcard = .none,
valid: bool = true,
- version: Version = Version{},
+ version: Version.Partial = .{},
stopped_at: u32 = 0,
};
@@ -1096,8 +1124,8 @@ pub const Range = struct {
gte = 6,
};
- left: Comparator = Comparator{},
- right: Comparator = Comparator{},
+ left: Comparator = .{},
+ right: Comparator = .{},
/// *
/// >= 0.0.0
@@ -1106,14 +1134,14 @@ pub const Range = struct {
/// >= x
/// >= 0
pub fn anyRangeSatisfies(this: *const Range) bool {
- return this.left.op == .gte and this.left.version.eql(Version{});
+ return this.left.op == .gte and this.left.version.eql(.{});
}
pub fn initWildcard(version: Version, wildcard: Query.Token.Wildcard) Range {
switch (wildcard) {
.none => {
- return Range{
- .left = Comparator{
+ return .{
+ .left = .{
.op = Op.eql,
.version = version,
},
@@ -1121,55 +1149,52 @@ pub const Range = struct {
},
.major => {
- return Range{
- .left = Comparator{
+ return .{
+ .left = .{
.op = Op.gte,
- .version = Version{
+ .version = .{
// .raw = version.raw
},
},
};
},
.minor => {
- var lhs = Version{
+ const lhs = Version{
+ .major = version.major +| 1,
// .raw = version.raw
};
- lhs.major = version.major + 1;
-
- var rhs = Version{
+ const rhs = Version{
+ .major = version.major,
// .raw = version.raw
};
- rhs.major = version.major;
-
- return Range{
- .left = Comparator{
+ return .{
+ .left = .{
.op = Op.lt,
.version = lhs,
},
- .right = Comparator{
+ .right = .{
.op = Op.gte,
.version = rhs,
},
};
},
.patch => {
- var lhs = Version{};
- lhs.major = version.major;
- lhs.minor = version.minor + 1;
-
- var rhs = Version{};
- rhs.major = version.major;
- rhs.minor = version.minor;
-
- // rhs.raw = version.raw;
- // lhs.raw = version.raw;
-
+ const lhs = Version{
+ .major = version.major,
+ .minor = version.minor +| 1,
+ // .raw = version.raw;
+ };
+ const rhs = Version{
+ .major = version.major,
+ .minor = version.minor,
+ // .raw = version.raw;
+ };
return Range{
- .left = Comparator{
+ .left = .{
.op = Op.lt,
.version = lhs,
},
- .right = Comparator{
+ .right = .{
.op = Op.gte,
.version = rhs,
},
@@ -1193,8 +1218,8 @@ pub const Range = struct {
}
pub const Comparator = struct {
- op: Op = Op.unset,
- version: Version = Version{},
+ op: Op = .unset,
+ version: Version = .{},
pub inline fn eql(lhs: Comparator, rhs: Comparator) bool {
return lhs.op == rhs.op and lhs.version.eql(rhs.version);
@@ -1405,83 +1430,95 @@ pub const Query = struct {
}
pub fn satisfies(this: *const Query, version: Version) bool {
- const left = this.range.satisfies(version);
-
- return left and (this.next orelse return true).satisfies(version);
+ return this.range.satisfies(version) and (this.next orelse return true).satisfies(version);
}
- pub const Token = struct {
+ const Token = struct {
tag: Tag = Tag.none,
wildcard: Wildcard = Wildcard.none,
- pub fn toRange(this: Token, version: Version) Range {
+ pub fn toRange(this: Token, version: Version.Partial) Range {
switch (this.tag) {
// Allows changes that do not modify the left-most non-zero element in the [major, minor, patch] tuple
.caret => {
- var right_version = version;
- // https://github.com/npm/node-semver/blob/cb1ca1d5480a6c07c12ac31ba5f2071ed530c4ed/classes/range.js#L310-L336
- if (right_version.major == 0) {
- if (right_version.minor == 0) {
- right_version.patch += 1;
- } else {
- right_version.minor += 1;
- right_version.patch = 0;
- }
- } else {
- right_version.major += 1;
- right_version.patch = 0;
- right_version.minor = 0;
- }
-
- return Range{
- .left = .{
+ // https://github.com/npm/node-semver/blob/3a8a4309ae986c1967b3073ba88c9e69433d44cb/classes/range.js#L302-L353
+ var range = Range{};
+ if (version.major) |major| done: {
+ range.left = .{
.op = .gte,
- .version = version,
- },
- .right = .{
+ .version = .{
+ .major = major,
+ },
+ };
+ range.right = .{
.op = .lt,
- .version = right_version,
- },
- };
+ };
+ if (version.minor) |minor| {
+ range.left.version.minor = minor;
+ if (version.patch) |patch| {
+ range.left.version.patch = patch;
+ range.left.version.tag = version.tag;
+ if (major == 0) {
+ if (minor == 0) {
+ range.right.version.patch = patch +| 1;
+ } else {
+ range.right.version.minor = minor +| 1;
+ }
+ break :done;
+ }
+ } else if (major == 0) {
+ range.right.version.minor = minor +| 1;
+ break :done;
+ }
+ }
+ range.right.version.major = major +| 1;
+ }
+ return range;
},
.tilda => {
- if (this.wildcard == .minor or this.wildcard == .major) {
- return Range.initWildcard(version, .minor);
- }
-
- // This feels like it needs to be tested more.
- var right_version = version;
- right_version.minor += 1;
- right_version.patch = 0;
-
- return Range{
- .left = .{
+ // https://github.com/npm/node-semver/blob/3a8a4309ae986c1967b3073ba88c9e69433d44cb/classes/range.js#L261-L287
+ var range = Range{};
+ if (version.major) |major| done: {
+ range.left = .{
.op = .gte,
- .version = version,
- },
- .right = .{
+ .version = .{
+ .major = major,
+ },
+ };
+ range.right = .{
.op = .lt,
- .version = right_version,
- },
- };
+ };
+ if (version.minor) |minor| {
+ range.left.version.minor = minor;
+ if (version.patch) |patch| {
+ range.left.version.patch = patch;
+ range.left.version.tag = version.tag;
+ }
+ range.right.version.major = major;
+ range.right.version.minor = minor +| 1;
+ break :done;
+ }
+ range.right.version.major = major +| 1;
+ }
+ return range;
},
.none => unreachable,
.version => {
if (this.wildcard != Wildcard.none) {
- return Range.initWildcard(version, this.wildcard);
+ return Range.initWildcard(version.fill(), this.wildcard);
}
- return Range{ .left = .{ .op = .eql, .version = version } };
+ return .{ .left = .{ .op = .eql, .version = version.fill() } };
},
else => {},
}
return switch (this.wildcard) {
- .major => Range{
- .left = .{ .op = .gte, .version = version },
+ .major => .{
+ .left = .{ .op = .gte, .version = version.fill() },
.right = .{
.op = .lte,
- .version = Version{
+ .version = .{
.major = std.math.maxInt(u32),
.minor = std.math.maxInt(u32),
.patch = std.math.maxInt(u32),
@@ -1489,43 +1526,43 @@ pub const Query = struct {
},
},
.minor => switch (this.tag) {
- .lte => Range{
+ .lte => .{
.left = .{
.op = .lte,
- .version = Version{
- .major = version.major,
+ .version = .{
+ .major = version.major orelse 0,
.minor = std.math.maxInt(u32),
.patch = std.math.maxInt(u32),
},
},
},
- .lt => Range{
+ .lt => .{
.left = .{
.op = .lt,
- .version = Version{
- .major = version.major,
+ .version = .{
+ .major = version.major orelse 0,
.minor = 0,
.patch = 0,
},
},
},
- .gt => Range{
+ .gt => .{
.left = .{
.op = .gt,
- .version = Version{
- .major = version.major,
+ .version = .{
+ .major = version.major orelse 0,
.minor = std.math.maxInt(u32),
.patch = std.math.maxInt(u32),
},
},
},
- .gte => Range{
+ .gte => .{
.left = .{
.op = .gte,
- .version = Version{
- .major = version.major,
+ .version = .{
+ .major = version.major orelse 0,
.minor = 0,
.patch = 0,
},
@@ -1534,51 +1571,51 @@ pub const Query = struct {
else => unreachable,
},
.patch => switch (this.tag) {
- .lte => Range{
+ .lte => .{
.left = .{
.op = .lte,
- .version = Version{
- .major = version.major,
- .minor = version.minor,
+ .version = .{
+ .major = version.major orelse 0,
+ .minor = version.minor orelse 0,
.patch = std.math.maxInt(u32),
},
},
},
- .lt => Range{
+ .lt => .{
.left = .{
.op = .lt,
- .version = Version{
- .major = version.major,
- .minor = version.minor,
+ .version = .{
+ .major = version.major orelse 0,
+ .minor = version.minor orelse 0,
.patch = 0,
},
},
},
- .gt => Range{
+ .gt => .{
.left = .{
.op = .gt,
- .version = Version{
- .major = version.major,
- .minor = version.minor,
+ .version = .{
+ .major = version.major orelse 0,
+ .minor = version.minor orelse 0,
.patch = std.math.maxInt(u32),
},
},
},
- .gte => Range{
+ .gte => .{
.left = .{
.op = .gte,
- .version = Version{
- .major = version.major,
- .minor = version.minor,
+ .version = .{
+ .major = version.major orelse 0,
+ .minor = version.minor orelse 0,
.patch = 0,
},
},
},
else => unreachable,
},
- .none => Range{
+ .none => .{
.left = .{
.op = switch (this.tag) {
.gt => .gt,
@@ -1587,7 +1624,7 @@ pub const Query = struct {
.lte => .lte,
else => unreachable,
},
- .version = version,
+ .version = version.fill(),
},
},
};
@@ -1706,8 +1743,9 @@ pub const Query = struct {
if (!skip_round) {
const parse_result = Version.parse(sliced.sub(input[i..]), allocator);
- if (parse_result.version.tag.hasBuild()) list.flags.setValue(Group.Flags.build, true);
- if (parse_result.version.tag.hasPre()) list.flags.setValue(Group.Flags.pre, true);
+ const version = parse_result.version.fill();
+ if (version.tag.hasBuild()) list.flags.setValue(Group.Flags.build, true);
+ if (version.tag.hasPre()) list.flags.setValue(Group.Flags.pre, true);
token.wildcard = parse_result.wildcard;
@@ -1740,42 +1778,42 @@ pub const Query = struct {
i += @as(usize, @boolToInt(!hyphenate));
if (hyphenate) {
- var second_version = Version.parse(sliced.sub(input[i..]), allocator);
- if (second_version.version.tag.hasBuild()) list.flags.setValue(Group.Flags.build, true);
- if (second_version.version.tag.hasPre()) list.flags.setValue(Group.Flags.pre, true);
-
+ const second_parsed = Version.parse(sliced.sub(input[i..]), allocator);
+ var second_version = second_parsed.version.fill();
+ if (second_version.tag.hasBuild()) list.flags.setValue(Group.Flags.build, true);
+ if (second_version.tag.hasPre()) list.flags.setValue(Group.Flags.pre, true);
const range: Range = brk: {
- switch (second_version.wildcard) {
+ switch (second_parsed.wildcard) {
.major => {
- second_version.version.major += 1;
+ second_version.major +|= 1;
break :brk Range{
- .left = .{ .op = .gte, .version = parse_result.version },
- .right = .{ .op = .lte, .version = second_version.version },
+ .left = .{ .op = .gte, .version = version },
+ .right = .{ .op = .lte, .version = second_version },
};
},
.minor => {
- second_version.version.major += 1;
- second_version.version.minor = 0;
- second_version.version.patch = 0;
+ second_version.major +|= 1;
+ second_version.minor = 0;
+ second_version.patch = 0;
break :brk Range{
- .left = .{ .op = .gte, .version = parse_result.version },
- .right = .{ .op = .lt, .version = second_version.version },
+ .left = .{ .op = .gte, .version = version },
+ .right = .{ .op = .lt, .version = second_version },
};
},
.patch => {
- second_version.version.minor += 1;
- second_version.version.patch = 0;
+ second_version.minor +|= 1;
+ second_version.patch = 0;
break :brk Range{
- .left = .{ .op = .gte, .version = parse_result.version },
- .right = .{ .op = .lt, .version = second_version.version },
+ .left = .{ .op = .gte, .version = version },
+ .right = .{ .op = .lt, .version = second_version },
};
},
.none => {
break :brk Range{
- .left = .{ .op = .gte, .version = parse_result.version },
- .right = .{ .op = .lte, .version = second_version.version },
+ .left = .{ .op = .gte, .version = version },
+ .right = .{ .op = .lte, .version = second_version },
};
},
}
@@ -1787,11 +1825,11 @@ pub const Query = struct {
try list.andRange(range);
}
- i += second_version.stopped_at + 1;
+ i += second_parsed.stopped_at + 1;
} else if (count == 0 and token.tag == .version) {
switch (parse_result.wildcard) {
.none => {
- try list.orVersion(parse_result.version);
+ try list.orVersion(version);
},
else => {
try list.orRange(token.toRange(parse_result.version));
@@ -1825,7 +1863,7 @@ pub const Query = struct {
}
};
-const expect = struct {
+const expect = if (Environment.isTest) struct {
pub var counter: usize = 0;
pub fn isRangeMatch(input: string, version_str: string) bool {
var parsed = Version.parse(SlicedString.init(version_str, version_str), default_allocator);
@@ -1838,7 +1876,7 @@ const expect = struct {
SlicedString.init(input, input),
) catch |err| Output.panic("Test fail due to error {s}", .{@errorName(err)});
- return list.satisfies(parsed.version);
+ return list.satisfies(parsed.version.fill());
}
pub fn range(input: string, version_str: string, src: std.builtin.SourceLocation) void {
@@ -1876,15 +1914,14 @@ const expect = struct {
counter = 0;
}
- pub fn version(input: string, v: [3]u32, src: std.builtin.SourceLocation) void {
+ pub fn version(input: string, v: [3]?u32, src: std.builtin.SourceLocation) void {
Output.initTest();
defer counter += 1;
- var result = Version.parse(SlicedString.init(input, input), default_allocator);
- var other = Version{ .major = v[0], .minor = v[1], .patch = v[2] };
+ const result = Version.parse(SlicedString.init(input, input), default_allocator);
std.debug.assert(result.valid);
- if (!other.eql(result.version)) {
- Output.panic("<r><red>Fail<r> Expected version <b>\"{s}\"<r> to match <b>\"{d}.{d}.{d}\" but received <red>\"{d}.{d}.{d}\"<r>\nAt: <blue><b>{s}:{d}:{d}<r><d> in {s}<r>", .{
+ if (v[0] != result.version.major or v[1] != result.version.minor or v[2] != result.version.patch) {
+ Output.panic("<r><red>Fail<r> Expected version <b>\"{s}\"<r> to match <b>\"{?d}.{?d}.{?d}\" but received <red>\"{?d}.{?d}.{?d}\"<r>\nAt: <blue><b>{s}:{d}:{d}<r><d> in {s}<r>", .{
input,
v[0],
v[1],
@@ -1905,11 +1942,15 @@ const expect = struct {
defer counter += 1;
var result = Version.parse(SlicedString.init(input, input), default_allocator);
- if (!v.eql(result.version)) {
- Output.panic("<r><red>Fail<r> Expected version <b>\"{s}\"<r> to match <b>\"{s}\" but received <red>\"{}\"<r>\nAt: <blue><b>{s}:{d}:{d}<r><d> in {s}<r>", .{
+ if (!v.eql(result.version.fill())) {
+ Output.panic("<r><red>Fail<r> Expected version <b>\"{s}\"<r> to match <b>\"{?d}.{?d}.{?d}\" but received <red>\"{?d}.{?d}.{?d}\"<r>\nAt: <blue><b>{s}:{d}:{d}<r><d> in {s}<r>", .{
input,
- v,
- result.version,
+ v.major,
+ v.minor,
+ v.patch,
+ result.version.major,
+ result.version.minor,
+ result.version.patch,
src.file,
src.line,
src.column,
@@ -1917,10 +1958,11 @@ const expect = struct {
});
}
}
-};
+} else {};
test "Version parsing" {
defer expect.done(@src());
+ const X: ?u32 = null;
expect.version("1.0.0", .{ 1, 0, 0 }, @src());
expect.version("1.1.0", .{ 1, 1, 0 }, @src());
@@ -1930,24 +1972,28 @@ test "Version parsing" {
expect.version("0.0.1", .{ 0, 0, 1 }, @src());
expect.version("0.0.0", .{ 0, 0, 0 }, @src());
- expect.version("1.x", .{ 1, 0, 0 }, @src());
- expect.version("2.2.x", .{ 2, 2, 0 }, @src());
- expect.version("2.x.2", .{ 2, 0, 2 }, @src());
+ expect.version("*", .{ X, X, X }, @src());
+ expect.version("x", .{ X, X, X }, @src());
+ expect.version("0", .{ 0, X, X }, @src());
+ expect.version("0.0", .{ 0, 0, X }, @src());
+ expect.version("0.0.0", .{ 0, 0, 0 }, @src());
- expect.version("1.X", .{ 1, 0, 0 }, @src());
- expect.version("2.2.X", .{ 2, 2, 0 }, @src());
- expect.version("2.X.2", .{ 2, 0, 2 }, @src());
+ expect.version("1.x", .{ 1, X, X }, @src());
+ expect.version("2.2.x", .{ 2, 2, X }, @src());
+ expect.version("2.x.2", .{ 2, X, 2 }, @src());
- expect.version("1.*", .{ 1, 0, 0 }, @src());
- expect.version("2.2.*", .{ 2, 2, 0 }, @src());
- expect.version("2.*.2", .{ 2, 0, 2 }, @src());
- expect.version("3", .{ 3, 0, 0 }, @src());
- expect.version("3.x", .{ 3, 0, 0 }, @src());
- expect.version("3.x.x", .{ 3, 0, 0 }, @src());
- expect.version("3.*.*", .{ 3, 0, 0 }, @src());
- expect.version("3.X.x", .{ 3, 0, 0 }, @src());
+ expect.version("1.X", .{ 1, X, X }, @src());
+ expect.version("2.2.X", .{ 2, 2, X }, @src());
+ expect.version("2.X.2", .{ 2, X, 2 }, @src());
- expect.version("0.0.0", .{ 0, 0, 0 }, @src());
+ expect.version("1.*", .{ 1, X, X }, @src());
+ expect.version("2.2.*", .{ 2, 2, X }, @src());
+ expect.version("2.*.2", .{ 2, X, 2 }, @src());
+ expect.version("3", .{ 3, X, X }, @src());
+ expect.version("3.x", .{ 3, X, X }, @src());
+ expect.version("3.x.x", .{ 3, X, X }, @src());
+ expect.version("3.*.*", .{ 3, X, X }, @src());
+ expect.version("3.X.x", .{ 3, X, X }, @src());
{
var v = Version{
@@ -2007,7 +2053,7 @@ test "Version parsing" {
var buf: [1024]u8 = undefined;
- var triplet = [3]u32{ 0, 0, 0 };
+ var triplet = [3]?u32{ null, null, null };
var x: u32 = 0;
var y: u32 = 0;
var z: u32 = 0;