use core::cmp::Ordering;
use core::fmt;
use bun_core::strings;
use crate::ExternalString;
use crate::SlicedString;
use crate::String as SemverString;
use crate::query::token::Wildcard;
pub type Version = VersionType<u64>;
pub trait VersionInt: Copy + Default + Eq + Ord + fmt::Display + 'static {
const ZERO: Self;
const MAX: Self;
type TagPadding: Copy + Default + 'static;
fn parse_ascii(s: &[u8]) -> Option<Self>;
}
impl VersionInt for u64 {
const ZERO: Self = 0;
const MAX: Self = u64::MAX;
type TagPadding = [u8; 0];
#[inline]
fn parse_ascii(s: &[u8]) -> Option<Self> {
bun_core::parse_unsigned::<u64>(s, 10).ok()
}
}
impl VersionInt for u32 {
const ZERO: Self = 0;
const MAX: Self = u32::MAX;
type TagPadding = [u8; 4];
#[inline]
fn parse_ascii(s: &[u8]) -> Option<Self> {
bun_core::parse_unsigned::<u32>(s, 10).ok()
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct VersionType<T: VersionInt> {
pub major: T,
pub minor: T,
pub patch: T,
#[doc(hidden)]
pub _tag_padding: T::TagPadding,
pub tag: Tag,
}
const _: () = {
assert!(core::mem::size_of::<Tag>() == 32);
assert!(core::mem::align_of::<Tag>() == 8);
assert!(core::mem::size_of::<VersionType<u64>>() == 56);
assert!(core::mem::size_of::<VersionType<u32>>() == 48);
assert!(core::mem::offset_of!(VersionType<u64>, tag) == 24);
assert!(core::mem::offset_of!(VersionType<u32>, tag) == 16);
};
impl<T: VersionInt> Default for VersionType<T> {
fn default() -> Self {
Self {
major: T::ZERO,
minor: T::ZERO,
patch: T::ZERO,
_tag_padding: Default::default(),
tag: Tag::default(),
}
}
}
impl VersionType<u32> {
pub fn migrate(self) -> VersionType<u64> {
VersionType {
major: u64::from(self.major),
minor: u64::from(self.minor),
patch: u64::from(self.patch),
_tag_padding: [],
tag: Tag {
pre: self.tag.pre,
build: self.tag.build,
},
}
}
}
impl<T: VersionInt> VersionType<T> {
pub fn sort_gt(ctx: &[u8], lhs: Self, rhs: Self) -> bool {
Self::order_fn(ctx, lhs, rhs) == Ordering::Greater
}
pub fn order_fn(ctx: &[u8], lhs: Self, rhs: Self) -> Ordering {
lhs.order(rhs, ctx, ctx)
}
pub fn is_zero(self) -> bool {
self.patch == T::ZERO && self.minor == T::ZERO && self.major == T::ZERO
}
pub fn parse_utf8(slice: &[u8]) -> ParseResult<T> {
Self::parse(SlicedString { buf: slice, slice })
}
pub fn clone_into(self, slice: &[u8], buf: &mut &mut [u8]) -> Self {
Self {
major: self.major,
minor: self.minor,
patch: self.patch,
_tag_padding: Default::default(),
tag: self.tag.clone_into(slice, buf),
}
}
#[inline]
pub fn len(&self) -> u32 {
(self.tag.build.len() + self.tag.pre.len()) as u32
}
pub fn fmt<'a>(self, input: &'a [u8]) -> Formatter<'a, T> {
Formatter {
version: self,
input,
}
}
pub fn diff_fmt<'a>(
self,
other: Self,
this_buf: &'a [u8],
other_buf: &'a [u8],
) -> DiffFormatter<'a, T> {
DiffFormatter {
version: self,
buf: this_buf,
other,
other_buf,
}
}
pub fn which_version_is_different(
left: Self,
right: Self,
left_buf: &[u8],
right_buf: &[u8],
) -> Option<ChangedVersion> {
if left.major != right.major {
return Some(ChangedVersion::Major);
}
if left.minor != right.minor {
return Some(ChangedVersion::Minor);
}
if left.patch != right.patch {
return Some(ChangedVersion::Patch);
}
if left.tag.has_pre() != right.tag.has_pre() {
return Some(ChangedVersion::Pre);
}
if !left.tag.has_pre() && !right.tag.has_pre() {
return None;
}
if left.tag.order_pre(right.tag, left_buf, right_buf) != Ordering::Equal {
return Some(ChangedVersion::Pre);
}
if left.tag.has_build() != right.tag.has_build() {
return Some(ChangedVersion::Build);
}
if !left.tag.has_build() && !right.tag.has_build() {
return None;
}
if left.tag.build.order(&right.tag.build, left_buf, right_buf) != Ordering::Equal {
Some(ChangedVersion::Build)
} else {
None
}
}
pub fn count<B>(&self, buf: &[u8], builder: &mut B)
where
B: crate::StringBuilder,
{
if self.tag.has_pre() && !self.tag.pre.is_inline() {
builder.count(self.tag.pre.slice(buf));
}
if self.tag.has_build() && !self.tag.build.is_inline() {
builder.count(self.tag.build.slice(buf));
}
}
pub fn append<B>(&self, buf: &[u8], builder: &mut B) -> Self
where
B: crate::StringBuilder,
{
let mut that = *self;
if self.tag.has_pre() && !self.tag.pre.is_inline() {
that.tag.pre = builder.append::<ExternalString>(self.tag.pre.slice(buf));
}
if self.tag.has_build() && !self.tag.build.is_inline() {
that.tag.build = builder.append::<ExternalString>(self.tag.build.slice(buf));
}
that
}
pub fn eql(self, rhs: Self) -> bool {
self.major == rhs.major
&& self.minor == rhs.minor
&& self.patch == rhs.patch
&& rhs.tag.eql(self.tag)
}
pub fn which_version_is_pinned(input: &[u8]) -> PinnedVersion {
let version = strings::trim(input, &strings::WHITESPACE_CHARS);
let mut i: usize;
let pinned: PinnedVersion = 'pinned: {
for j in 0..version.len() {
match version[j] {
b' '
| b'\t'
| b'\n'
| b'\r'
| 0x0B | 0x0C
| b'v'
| b'=' => {}
c => {
i = j;
match c {
b'~' | b'^' => {
i += 1;
for k in i..version.len() {
match version[k] {
b' '
| b'\t'
| b'\n'
| b'\r'
| 0x0B
| 0x0C => {
}
_ => {
i = k;
break 'pinned if c == b'~' {
PinnedVersion::Minor
} else {
PinnedVersion::Major
};
}
}
}
return PinnedVersion::Major;
}
b'0'..=b'9' => break 'pinned PinnedVersion::Patch,
_ => return PinnedVersion::Major,
}
}
}
}
return PinnedVersion::Major;
};
if i >= version.len() || !version[i].is_ascii_digit() {
return PinnedVersion::Major;
}
let mut d = version[i];
while d.is_ascii_digit() {
i += 1;
if i >= version.len() {
return if pinned == PinnedVersion::Patch {
PinnedVersion::Major
} else {
pinned
};
}
d = version[i];
}
if d != b'.' {
return PinnedVersion::Major;
}
i += 1;
if i >= version.len() || !version[i].is_ascii_digit() {
return PinnedVersion::Major;
}
d = version[i];
while d.is_ascii_digit() {
i += 1;
if i >= version.len() {
return if pinned == PinnedVersion::Patch {
PinnedVersion::Minor
} else {
pinned
};
}
d = version[i];
}
if d != b'.' {
return PinnedVersion::Major;
}
i += 1;
if i >= version.len() || !version[i].is_ascii_digit() {
return PinnedVersion::Major;
}
d = version[i];
while d.is_ascii_digit() {
i += 1;
if i >= version.len() {
return pinned;
}
d = version[i];
}
if !valid_pre_or_build_tag_character(d) || d.is_ascii_whitespace() {
return PinnedVersion::Major;
}
i += 1;
if i >= version.len() {
return pinned;
}
d = version[i];
while valid_pre_or_build_tag_character(d) && !d.is_ascii_whitespace() {
i += 1;
if i >= version.len() {
return pinned;
}
d = version[i];
}
PinnedVersion::Major
}
pub fn is_tagged_version_only(input: &[u8]) -> bool {
let version = strings::trim(input, &strings::WHITESPACE_CHARS);
if version.is_empty() || !version[0].is_ascii_alphabetic() {
return false;
}
for i in 1..version.len() {
if !version[i].is_ascii_alphanumeric() {
return false;
}
}
true
}
pub fn order_without_tag(lhs: Self, rhs: Self) -> Ordering {
if lhs.major < rhs.major {
return Ordering::Less;
}
if lhs.major > rhs.major {
return Ordering::Greater;
}
if lhs.minor < rhs.minor {
return Ordering::Less;
}
if lhs.minor > rhs.minor {
return Ordering::Greater;
}
if lhs.patch < rhs.patch {
return Ordering::Less;
}
if lhs.patch > rhs.patch {
return Ordering::Greater;
}
if lhs.tag.has_pre() {
if !rhs.tag.has_pre() {
return Ordering::Less;
}
} else {
if rhs.tag.has_pre() {
return Ordering::Greater;
}
}
Ordering::Equal
}
pub fn order(self, rhs: Self, lhs_buf: &[u8], rhs_buf: &[u8]) -> Ordering {
let order_without_tag = Self::order_without_tag(self, rhs);
if order_without_tag != Ordering::Equal {
return order_without_tag;
}
self.tag.order(rhs.tag, lhs_buf, rhs_buf)
}
pub fn order_without_build(self, rhs: Self, lhs_buf: &[u8], rhs_buf: &[u8]) -> Ordering {
let order_without_tag = Self::order_without_tag(self, rhs);
if order_without_tag != Ordering::Equal {
return order_without_tag;
}
self.tag.order_without_build(rhs.tag, lhs_buf, rhs_buf)
}
pub fn parse(sliced_string: SlicedString) -> ParseResult<T> {
let input = sliced_string.slice;
let mut result = ParseResult::<T>::default();
let mut part_i: u8 = 0;
let mut part_start_i: usize;
let mut last_char_i: usize = 0;
if input.is_empty() {
result.valid = false;
return result;
}
let mut is_done = false;
let mut i: usize = 0;
for c in 0..input.len() {
match input[c] {
b' '
| b'\t'
| b'\n'
| b'\r'
| 0x0B | 0x0C
| b'v'
| b'=' => {}
_ => {
i = c;
break;
}
}
}
if i == input.len() {
result.valid = false;
return result;
}
while i < input.len() {
if is_done {
break;
}
match input[i] {
b' ' => {
is_done = true;
break;
}
b'|' | b'^' | b'#' | b'&' | b'%' | b'!' => {
is_done = true;
i = i.saturating_sub(1);
break;
}
b'0'..=b'9' => {
part_start_i = i;
i += 1;
while i < input.len() && input[i].is_ascii_digit() {
i += 1;
}
last_char_i = i;
match part_i {
0 => {
result.version.major =
Self::parse_version_number(&input[part_start_i..last_char_i]);
part_i = 1;
}
1 => {
result.version.minor =
Self::parse_version_number(&input[part_start_i..last_char_i]);
part_i = 2;
}
2 => {
result.version.patch =
Self::parse_version_number(&input[part_start_i..last_char_i]);
part_i = 3;
}
_ => {}
}
if i < input.len()
&& match input[i] {
b'.' => part_i != 3,
_ => false,
}
{
i += 1;
}
}
b'.' => {
result.valid = false;
is_done = true;
break;
}
b'-' | b'+' => {
if part_i < 2 && result.wildcard == Wildcard::None {
result.valid = false;
is_done = true;
break;
}
part_start_i = i;
while i < input.len() && matches!(input[i], b' ') {
i += 1;
}
let tag_result = Tag::parse(sliced_string.sub(&input[part_start_i..]));
result.version.tag = tag_result.tag;
i += tag_result.len as usize;
break;
}
b'x' | b'*' | b'X' => {
i += 1;
while i < input.len() && matches!(input[i], b'x' | b'*' | b'X') {
i += 1;
}
last_char_i = i;
if i < input.len() && matches!(input[i], b'.') {
i += 1;
}
if result.wildcard == Wildcard::None {
match part_i {
0 => {
result.wildcard = Wildcard::Major;
part_i = 1;
}
1 => {
result.wildcard = Wildcard::Minor;
part_i = 2;
}
2 => {
result.wildcard = Wildcard::Patch;
part_i = 3;
}
_ => {}
}
}
}
c => {
if result.wildcard == Wildcard::None && part_i >= 2 && c.is_ascii_alphabetic() {
part_start_i = i;
let tag_result =
Tag::parse_with_pre_count(sliced_string.sub(&input[part_start_i..]), 1);
result.version.tag = tag_result.tag;
i += tag_result.len as usize;
is_done = true;
last_char_i = i;
break;
}
last_char_i = 0;
result.valid = false;
is_done = true;
break;
}
}
}
let _ = last_char_i;
let _ = is_done;
if result.wildcard == Wildcard::None {
match part_i {
0 => {
result.wildcard = Wildcard::Major;
}
1 => {
result.wildcard = Wildcard::Minor;
}
2 => {
result.wildcard = Wildcard::Patch;
}
_ => {}
}
}
result.len = u32::try_from(i).expect("int cast");
result
}
fn parse_version_number(input: &[u8]) -> Option<T> {
let mut bytes = [0u8; 20];
let mut byte_i: u8 = 0;
debug_assert!(input[0] != b'.');
for &char in input {
match char {
b'X' | b'x' | b'*' => return None,
b'0'..=b'9' => {
if (byte_i as usize) + 1 > bytes.len() {
return None;
}
bytes[byte_i as usize] = char;
byte_i += 1;
}
b' ' | b'.' => break,
_ => {}
}
}
if byte_i == 0 {
return None;
}
if cfg!(debug_assertions) {
return match T::parse_ascii(&bytes[0..byte_i as usize]) {
Some(v) => Some(v),
None => {
bun_core::pretty_errorln!(
"ERROR parsing version: \"{}\", bytes: {}",
bstr::BStr::new(input),
bstr::BStr::new(&bytes[0..byte_i as usize]),
);
Some(T::ZERO)
}
};
}
Some(T::parse_ascii(&bytes[0..byte_i as usize]).unwrap_or(T::ZERO))
}
}
fn valid_pre_or_build_tag_character(c: u8) -> bool {
matches!(c, b'-' | b'+' | b'.' | b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9')
}
pub struct Formatter<'a, T: VersionInt> {
pub version: VersionType<T>,
pub input: &'a [u8],
}
impl<'a, T: VersionInt> fmt::Display for Formatter<'a, T> {
fn fmt(&self, writer: &mut fmt::Formatter<'_>) -> fmt::Result {
let self_ = self.version;
write!(writer, "{}.{}.{}", self_.major, self_.minor, self_.patch)?;
if self_.tag.has_pre() {
let pre = self_.tag.pre.slice(self.input);
write!(writer, "-{}", bstr::BStr::new(pre))?;
}
if self_.tag.has_build() {
let build = self_.tag.build.slice(self.input);
write!(writer, "+{}", bstr::BStr::new(build))?;
}
Ok(())
}
}
pub struct DiffFormatter<'a, T: VersionInt> {
pub version: VersionType<T>,
pub buf: &'a [u8],
pub other: VersionType<T>,
pub other_buf: &'a [u8],
}
impl<'a, T: VersionInt> fmt::Display for DiffFormatter<'a, T> {
fn fmt(&self, writer: &mut fmt::Formatter<'_>) -> fmt::Result {
use core::fmt::Write as _;
use core::sync::atomic::Ordering as AtomicOrdering;
if !bun_core::output::ENABLE_ANSI_COLORS_STDOUT.load(AtomicOrdering::Relaxed) {
let formatter = Formatter {
version: self.version,
input: self.buf,
};
return fmt::Display::fmt(&formatter, writer);
}
let diff = VersionType::which_version_is_different(
self.version,
self.other,
self.buf,
self.other_buf,
)
.unwrap_or(ChangedVersion::None);
match diff {
ChangedVersion::Major => write!(
writer,
concat!(bun_core::pretty_fmt!("<r><b><red>", true), "{}.{}.{}"),
self.version.major, self.version.minor, self.version.patch,
)?,
ChangedVersion::Minor => {
if self.version.major == T::ZERO {
write!(
writer,
concat!(
bun_core::pretty_fmt!("<d>", true),
"{}.",
bun_core::pretty_fmt!("<r><b><red>", true),
"{}.{}"
),
self.version.major, self.version.minor, self.version.patch,
)?;
} else {
write!(
writer,
concat!(
bun_core::pretty_fmt!("<d>", true),
"{}.",
bun_core::pretty_fmt!("<r><b><yellow>", true),
"{}.{}"
),
self.version.major, self.version.minor, self.version.patch,
)?;
}
}
ChangedVersion::Patch => {
if self.version.major == T::ZERO && self.version.minor == T::ZERO {
write!(
writer,
concat!(
bun_core::pretty_fmt!("<d>", true),
"{}.{}.",
bun_core::pretty_fmt!("<r><b><red>", true),
"{}"
),
self.version.major, self.version.minor, self.version.patch,
)?;
} else {
write!(
writer,
concat!(
bun_core::pretty_fmt!("<d>", true),
"{}.{}.",
bun_core::pretty_fmt!("<r><b><green>", true),
"{}"
),
self.version.major, self.version.minor, self.version.patch,
)?;
}
}
ChangedVersion::None | ChangedVersion::Pre | ChangedVersion::Build => write!(
writer,
concat!(bun_core::pretty_fmt!("<d>", true), "{}.{}.{}"),
self.version.major, self.version.minor, self.version.patch,
)?,
}
let mut set_color = false;
if self.version.tag.has_pre() {
if self.other.tag.has_pre() {
let pre = self.version.tag.pre.slice(self.buf);
let other_pre = self.other.tag.pre.slice(self.other_buf);
let mut first = true;
for (i, &c) in pre.iter().enumerate() {
if !set_color && i < other_pre.len() && c != other_pre[i] {
set_color = true;
writer.write_str(bun_core::pretty_fmt!("<r><b><red>", true))?;
}
if first {
first = false;
writer.write_char('-')?;
}
writer.write_char(c as char)?;
}
} else {
write!(
writer,
concat!(bun_core::pretty_fmt!("<r><b><red>", true), "-{}"),
self.version.tag.pre.fmt(self.buf),
)?;
set_color = true;
}
}
if self.version.tag.has_build() {
if self.other.tag.has_build() {
let build = self.version.tag.build.slice(self.buf);
let other_build = self.other.tag.build.slice(self.other_buf);
let mut first = true;
for (i, &c) in build.iter().enumerate() {
if !set_color && i < other_build.len() && c != other_build[i] {
set_color = true;
writer.write_str(bun_core::pretty_fmt!("<r><b><red>", true))?;
}
if first {
first = false;
writer.write_char('+')?;
}
writer.write_char(c as char)?;
}
} else {
if !set_color {
write!(
writer,
concat!(bun_core::pretty_fmt!("<r><b><red>", true), "+{}"),
self.version.tag.build.fmt(self.buf),
)?;
} else {
write!(writer, "+{}", self.version.tag.build.fmt(self.other_buf))?;
}
}
}
writer.write_str(bun_core::pretty_fmt!("<r>", true))?;
Ok(())
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum ChangedVersion {
Major,
Minor,
Patch,
Pre,
Build,
None,
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum PinnedVersion {
Major, Minor, Patch, }
#[derive(Copy, Clone)]
pub struct Partial<T: VersionInt> {
pub major: Option<T>,
pub minor: Option<T>,
pub patch: Option<T>,
pub tag: Tag,
}
impl<T: VersionInt> Default for Partial<T> {
fn default() -> Self {
Self {
major: None,
minor: None,
patch: None,
tag: Tag::default(),
}
}
}
impl<T: VersionInt> Partial<T> {
pub fn min(self) -> VersionType<T> {
VersionType {
major: self.major.unwrap_or(T::ZERO),
minor: self.minor.unwrap_or(T::ZERO),
patch: self.patch.unwrap_or(T::ZERO),
_tag_padding: Default::default(),
tag: self.tag,
}
}
pub fn max(self) -> VersionType<T> {
VersionType {
major: self.major.unwrap_or(T::MAX),
minor: self.minor.unwrap_or(T::MAX),
patch: self.patch.unwrap_or(T::MAX),
_tag_padding: Default::default(),
tag: self.tag,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct Tag {
pub pre: ExternalString,
pub build: ExternalString,
}
impl Tag {
pub fn order_pre(self, rhs: Tag, lhs_buf: &[u8], rhs_buf: &[u8]) -> Ordering {
let lhs_str = self.pre.slice(lhs_buf);
let rhs_str = rhs.pre.slice(rhs_buf);
let mut lhs_itr = strings::split(lhs_str, b".");
let mut rhs_itr = strings::split(rhs_str, b".");
loop {
let lhs_part = lhs_itr.next();
let rhs_part = rhs_itr.next();
if lhs_part.is_none() && rhs_part.is_none() {
return Ordering::Equal;
}
if rhs_part.is_none() {
return Ordering::Greater;
}
if lhs_part.is_none() {
return Ordering::Less;
}
let lhs_part = lhs_part.unwrap();
let rhs_part = rhs_part.unwrap();
let lhs_uint: Option<u64> = u64::parse_ascii(lhs_part);
let rhs_uint: Option<u64> = u64::parse_ascii(rhs_part);
if lhs_uint.is_some() && rhs_uint.is_none() {
return Ordering::Less;
}
if lhs_uint.is_none() && rhs_uint.is_some() {
return Ordering::Greater;
}
if lhs_uint.is_none() && rhs_uint.is_none() {
match strings::order(lhs_part, rhs_part) {
Ordering::Equal => {
continue;
}
not_equal => return not_equal,
}
}
match lhs_uint.unwrap().cmp(&rhs_uint.unwrap()) {
Ordering::Equal => continue,
not_equal => return not_equal,
}
}
}
pub fn order(self, rhs: Tag, lhs_buf: &[u8], rhs_buf: &[u8]) -> Ordering {
if !self.pre.is_empty() && !rhs.pre.is_empty() {
return self.order_pre(rhs, lhs_buf, rhs_buf);
}
let pre_order = self.pre.order(&rhs.pre, lhs_buf, rhs_buf);
if pre_order != Ordering::Equal {
return pre_order;
}
self.build.order(&rhs.build, lhs_buf, rhs_buf)
}
pub fn order_without_build(self, rhs: Tag, lhs_buf: &[u8], rhs_buf: &[u8]) -> Ordering {
if !self.pre.is_empty() && !rhs.pre.is_empty() {
return self.order_pre(rhs, lhs_buf, rhs_buf);
}
self.pre.order(&rhs.pre, lhs_buf, rhs_buf)
}
pub fn clone_into(self, slice: &[u8], buf: &mut &mut [u8]) -> Tag {
let pre: SemverString;
let build: SemverString;
if self.pre.is_inline() {
pre = self.pre.value;
} else {
let pre_slice = self.pre.slice(slice);
buf[..pre_slice.len()].copy_from_slice(pre_slice);
pre = SemverString::init(buf, &buf[0..pre_slice.len()]);
*buf = &mut core::mem::take(buf)[pre_slice.len()..];
}
if self.build.is_inline() {
build = self.build.value;
} else {
let build_slice = self.build.slice(slice);
buf[..build_slice.len()].copy_from_slice(build_slice);
build = SemverString::init(buf, &buf[0..build_slice.len()]);
*buf = &mut core::mem::take(buf)[build_slice.len()..];
}
Tag {
pre: ExternalString {
value: pre,
hash: self.pre.hash,
},
build: ExternalString {
value: build,
hash: self.build.hash,
},
}
}
#[inline]
pub fn has_pre(self) -> bool {
!self.pre.is_empty()
}
#[inline]
pub fn has_build(self) -> bool {
!self.build.is_empty()
}
pub fn eql(self, rhs: Tag) -> bool {
self.pre.hash == rhs.pre.hash
}
pub fn parse(sliced_string: SlicedString) -> TagResult {
Self::parse_with_pre_count(sliced_string, 0)
}
pub fn parse_with_pre_count(sliced_string: SlicedString, initial_pre_count: u32) -> TagResult {
let input = sliced_string.slice;
let mut build_count: u32 = 0;
let mut pre_count: u32 = initial_pre_count;
for &c in input {
match c {
b' ' => break,
b'+' => {
build_count += 1;
}
b'-' => {
pre_count += 1;
}
_ => {}
}
}
if build_count == 0 && pre_count == 0 {
return TagResult {
len: 0,
..Default::default()
};
}
#[derive(Copy, Clone, Eq, PartialEq)]
enum State {
None,
Pre,
Build,
}
let mut result = TagResult::default();
let mut state = State::None;
let mut start: usize = 0;
let mut i: usize = 0;
while i < input.len() {
let c = input[i];
match c {
b'+' => {
if state == State::Pre || state == State::None && initial_pre_count > 0 {
result.tag.pre = sliced_string.sub(&input[start..i]).external();
}
if state != State::Build {
state = State::Build;
start = i + 1;
}
}
b'-' => {
if state != State::Pre {
state = State::Pre;
start = i + 1;
}
}
b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'.' => {}
_ => {
match state {
State::None => {}
State::Pre => {
result.tag.pre = sliced_string.sub(&input[start..i]).external();
state = State::None;
}
State::Build => {
result.tag.build = sliced_string.sub(&input[start..i]).external();
if cfg!(debug_assertions) {
debug_assert!(!strings::contains_char(
result.tag.build.slice(sliced_string.buf),
b'-'
));
}
state = State::None;
}
}
result.len = i as u32; break;
}
}
i += 1;
}
if state == State::None && initial_pre_count > 0 {
state = State::Pre;
start = 0;
}
match state {
State::None => {}
State::Pre => {
result.tag.pre = sliced_string.sub(&input[start..i]).external();
}
State::Build => {
result.tag.build = sliced_string.sub(&input[start..i]).external();
}
}
result.len = i as u32;
result
}
}
#[derive(Copy, Clone, Default)]
pub struct TagResult {
pub tag: Tag,
pub len: u32,
}
#[derive(Copy, Clone)]
pub struct ParseResult<T: VersionInt> {
pub wildcard: Wildcard,
pub valid: bool,
pub version: Partial<T>,
pub len: u32,
}
impl<T: VersionInt> Default for ParseResult<T> {
fn default() -> Self {
Self {
wildcard: Wildcard::None,
valid: true,
version: Partial::default(),
len: 0,
}
}
}