Files
bun.sh/src/semver/SemverRange.zig

274 lines
7.7 KiB
Zig

pub const Op = enum(u8) {
unset = 0,
eql = 1,
lt = 3,
lte = 4,
gt = 5,
gte = 6,
};
left: Comparator = .{},
right: Comparator = .{},
pub fn format(this: Range, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
if (this.left.op == .unset and this.right.op == .unset) {
return;
}
if (this.right.op == .unset) {
try std.fmt.format(writer, "{}", .{this.left});
} else {
try std.fmt.format(writer, "{} {}", .{ this.left, this.right });
}
}
/// *
/// >= 0.0.0
/// >= 0
/// >= 0.0
/// >= x
/// >= 0
pub fn anyRangeSatisfies(this: *const Range) bool {
return this.left.op == .gte and this.left.version.eql(.{});
}
pub fn initWildcard(version: Version, wildcard: Query.Token.Wildcard) Range {
switch (wildcard) {
.none => {
return .{
.left = .{
.op = Op.eql,
.version = version,
},
};
},
.major => {
return .{
.left = .{
.op = Op.gte,
.version = .{
// .raw = version.raw
},
},
};
},
.minor => {
const lhs = Version{
.major = version.major +| 1,
// .raw = version.raw
};
const rhs = Version{
.major = version.major,
// .raw = version.raw
};
return .{
.left = .{
.op = Op.lt,
.version = lhs,
},
.right = .{
.op = Op.gte,
.version = rhs,
},
};
},
.patch => {
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 = .{
.op = Op.lt,
.version = lhs,
},
.right = .{
.op = Op.gte,
.version = rhs,
},
};
},
}
}
pub inline fn hasLeft(this: Range) bool {
return this.left.op != Op.unset;
}
pub inline fn hasRight(this: Range) bool {
return this.right.op != Op.unset;
}
/// Is the Range equal to another Range
/// This does not evaluate the range.
pub inline fn eql(lhs: Range, rhs: Range) bool {
return lhs.left.eql(rhs.left) and lhs.right.eql(rhs.right);
}
pub const Formatter = struct {
buffer: []const u8,
range: *const Range,
pub fn format(this: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
if (this.range.left.op == Op.unset and this.range.right.op == Op.unset) {
return;
}
if (this.range.right.op == .unset) {
try std.fmt.format(writer, "{}", .{this.range.left.fmt(this.buffer)});
} else {
try std.fmt.format(writer, "{} {}", .{ this.range.left.fmt(this.buffer), this.range.right.fmt(this.buffer) });
}
}
};
pub fn fmt(this: *const Range, buf: []const u8) @This().Formatter {
return .{ .buffer = buf, .range = this };
}
pub const Comparator = struct {
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);
}
pub const Formatter = struct {
buffer: []const u8,
comparator: *const Comparator,
pub fn format(this: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
if (this.comparator.op == Op.unset) {
return;
}
switch (this.comparator.op) {
.unset => unreachable, // see above,
.eql => try writer.writeAll("=="),
.lt => try writer.writeAll("<"),
.lte => try writer.writeAll("<="),
.gt => try writer.writeAll(">"),
.gte => try writer.writeAll(">="),
}
try std.fmt.format(writer, "{}", .{this.comparator.version.fmt(this.buffer)});
}
};
pub fn fmt(this: *const Comparator, buf: []const u8) @This().Formatter {
return .{ .buffer = buf, .comparator = this };
}
pub fn satisfies(
comparator: Comparator,
version: Version,
comparator_buf: string,
version_buf: string,
) bool {
const order = version.orderWithoutBuild(comparator.version, version_buf, comparator_buf);
return switch (order) {
.eq => switch (comparator.op) {
.lte, .gte, .eql => true,
else => false,
},
.gt => switch (comparator.op) {
.gt, .gte => true,
else => false,
},
.lt => switch (comparator.op) {
.lt, .lte => true,
else => false,
},
};
}
};
pub fn satisfies(range: Range, version: Version, range_buf: string, version_buf: string) bool {
const has_left = range.hasLeft();
const has_right = range.hasRight();
if (!has_left) {
return true;
}
if (!range.left.satisfies(version, range_buf, version_buf)) {
return false;
}
if (has_right and !range.right.satisfies(version, range_buf, version_buf)) {
return false;
}
return true;
}
pub fn satisfiesPre(range: Range, version: Version, range_buf: string, version_buf: string, pre_matched: *bool) bool {
if (comptime Environment.allow_assert) {
assert(version.tag.hasPre());
}
const has_left = range.hasLeft();
const has_right = range.hasRight();
if (!has_left) {
return true;
}
// If left has prerelease check if major,minor,patch matches with left. If
// not, check the same with right if right exists and has prerelease.
pre_matched.* = pre_matched.* or
(range.left.version.tag.hasPre() and
version.patch == range.left.version.patch and
version.minor == range.left.version.minor and
version.major == range.left.version.major) or
(has_right and
range.right.version.tag.hasPre() and
version.patch == range.right.version.patch and
version.minor == range.right.version.minor and
version.major == range.right.version.major);
if (!range.left.satisfies(version, range_buf, version_buf)) {
return false;
}
if (has_right and !range.right.satisfies(version, range_buf, version_buf)) {
return false;
}
return true;
}
const Range = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
const Environment = bun.Environment;
const strings = bun.strings;
const MutableString = bun.MutableString;
const stringZ = bun.stringZ;
const default_allocator = bun.default_allocator;
const C = bun.C;
const JSC = bun.JSC;
const IdentityContext = @import("../identity_context.zig").IdentityContext;
const OOM = bun.OOM;
const TruncatedPackageNameHash = bun.install.TruncatedPackageNameHash;
const Lockfile = bun.install.Lockfile;
const ExternalString = bun.Semver.ExternalString;
const SlicedString = bun.Semver.SlicedString;
const String = bun.Semver.String;
const Version = bun.Semver.Version;
const Query = bun.Semver.Query;
const assert = bun.assert;