const std = @import("std"); const mem = std.mem; const Order = std.math.Order; const GCCVersion = @This(); /// Raw version number text raw: []const u8 = "", /// -1 indicates not present major: i32 = -1, /// -1 indicates not present minor: i32 = -1, /// -1 indicates not present patch: i32 = -1, /// Text of parsed major version number major_str: []const u8 = "", /// Text of parsed major + minor version number minor_str: []const u8 = "", /// Patch number suffix suffix: []const u8 = "", /// This orders versions according to the preferred usage order, not a notion of release-time ordering /// Higher version numbers are preferred, but nonexistent minor/patch/suffix is preferred to one that does exist /// e.g. `4.1` is preferred over `4.0` but `4` is preferred over both `4.0` and `4.1` pub fn isLessThan(self: GCCVersion, rhs_major: i32, rhs_minor: i32, rhs_patch: i32, rhs_suffix: []const u8) bool { if (self.major != rhs_major) { return self.major < rhs_major; } if (self.minor != rhs_minor) { if (rhs_minor == -1) return true; if (self.minor == -1) return false; return self.minor < rhs_minor; } if (self.patch != rhs_patch) { if (rhs_patch == -1) return true; if (self.patch == -1) return false; return self.patch < rhs_patch; } if (!mem.eql(u8, self.suffix, rhs_suffix)) { if (rhs_suffix.len == 0) return true; if (self.suffix.len == 0) return false; return switch (std.mem.order(u8, self.suffix, rhs_suffix)) { .lt => true, .eq => unreachable, .gt => false, }; } return false; } /// Strings in the returned GCCVersion struct have the same lifetime as `text` pub fn parse(text: []const u8) GCCVersion { const bad = GCCVersion{ .major = -1 }; var good = bad; var it = mem.splitScalar(u8, text, '.'); const first = it.next().?; const second = it.next() orelse ""; const rest = it.next() orelse ""; good.major = std.fmt.parseInt(i32, first, 10) catch return bad; if (good.major < 0) return bad; good.major_str = first; if (second.len == 0) return good; var minor_str = second; if (rest.len == 0) { const end = mem.indexOfNone(u8, minor_str, "0123456789") orelse minor_str.len; if (end > 0) { good.suffix = minor_str[end..]; minor_str = minor_str[0..end]; } } good.minor = std.fmt.parseInt(i32, minor_str, 10) catch return bad; if (good.minor < 0) return bad; good.minor_str = minor_str; if (rest.len > 0) { const end = mem.indexOfNone(u8, rest, "0123456789") orelse rest.len; if (end > 0) { const patch_num_text = rest[0..end]; good.patch = std.fmt.parseInt(i32, patch_num_text, 10) catch return bad; if (good.patch < 0) return bad; good.suffix = rest[end..]; } } return good; } pub fn order(a: GCCVersion, b: GCCVersion) Order { if (a.isLessThan(b.major, b.minor, b.patch, b.suffix)) return .lt; if (b.isLessThan(a.major, a.minor, a.patch, a.suffix)) return .gt; return .eq; } test parse { const versions = [10]GCCVersion{ parse("5"), parse("4"), parse("4.2"), parse("4.0"), parse("4.0-patched"), parse("4.0.2"), parse("4.0.1"), parse("4.0.1-patched"), parse("4.0.0"), parse("4.0.0-patched"), }; for (versions[0 .. versions.len - 1], versions[1..versions.len]) |first, second| { try std.testing.expectEqual(Order.eq, first.order(first)); try std.testing.expectEqual(Order.gt, first.order(second)); try std.testing.expectEqual(Order.lt, second.order(first)); } const last = versions[versions.len - 1]; try std.testing.expectEqual(Order.eq, last.order(last)); }