pub mod parse_errors;
mod parser;
mod tests;
use {
alloc::string::{String, ToString},
core::{
cmp::Ordering,
ffi::CStr,
fmt::{self, Display, Formatter},
hash::{Hash, Hasher},
str::FromStr,
},
crate::{Error, PreRelease, Result},
self::parser::Parser,
};
#[cfg(feature="std")]
use std::ffi::OsStr;
const MAX_STR_LEN_OF_A_VERSION_NUMBER: usize = 20;
const PRE_RELEASE_STARTER: char = '-';
const BUILD_METADATA_STARTER: char = '+';
#[cfg(target_pointer_width = "8")]
const MAX_INPUT_STR_LEN: usize = 255;
#[cfg(not(target_pointer_width = "8"))]
const MAX_INPUT_STR_LEN: usize = 2048;
#[derive(Debug, Default, Clone, Eq)]
pub struct Semver {
major: u64,
minor: u64,
patch: u64,
pre_release: Option<String>,
build_metadata: Option<String>,
}
impl Semver {
pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
Semver {
major, minor, patch,
pre_release: None, build_metadata: None,
}
}
pub fn parse<S>(s: S) -> Result<Self> where S: AsRef<str> {
Parser::parse(s.as_ref(), true)
}
#[cfg(feature="std")]
fn parse_os_str_with_options<S>(s: S, strict: bool) -> Result<Self> where S: AsRef<OsStr> {
match s.as_ref().to_str() {
Some(s) => Parser::parse(s.as_ref(), strict),
None => Err(err!(parse_errors::INVALID_TOKEN)),
}
}
#[cfg(feature="std")]
pub fn parse_os_str<S>(s: S) -> Result<Self> where S: AsRef<OsStr> {
Self::parse_os_str_with_options(s, true)
}
#[cfg(feature="std")]
pub fn from_os_str<S>(s: S) -> Result<Self> where S: AsRef<OsStr> {
Self::parse_os_str_with_options(s, false)
}
fn parse_c_str_with_options<S>(s: S, strict: bool) -> Result<Self> where S: AsRef<CStr> {
match s.as_ref().to_str() {
Ok(s) => Parser::parse(s.as_ref(), strict),
_ => Err(err!(parse_errors::INVALID_TOKEN)),
}
}
pub fn parse_c_str<S>(s: S) -> Result<Self> where S: AsRef<CStr> {
Self::parse_c_str_with_options(s, true)
}
pub fn from_c_str<S>(s: S) -> Result<Self> where S: AsRef<CStr> {
Self::parse_c_str_with_options(s, false)
}
pub fn major(&self) -> u64 {
self.major
}
pub fn minor(&self) -> u64 {
self.minor
}
pub fn patch(&self) -> u64 {
self.patch
}
pub fn pre_release(&self) -> Option<&str> {
self.pre_release.as_ref().map(|pr| pr.as_str())
}
pub fn build_metadata(&self) -> Option<&str> {
self.build_metadata.as_ref().map(|bm| bm.as_str())
}
pub fn is_stable(&self) -> bool {
self.major > 0 && self.pre_release.is_none()
}
pub fn is_early(&self) -> bool {
self.major == 0
}
pub fn parse_pre_release(&self) -> Option<PreRelease> {
self.pre_release.as_ref().map(|pre_release| PreRelease::parse(pre_release))
}
pub fn compatible_with(&self, other: &Self) -> bool {
if self.major != other.major { return false; }
if self.major > 0 { return true; }
if self.minor != other.minor || self.patch != other.patch { return false; }
match (self.pre_release.as_ref(), other.pre_release.as_ref()) {
(Some(self_pre_release), Some(other_pre_release)) => self_pre_release == other_pre_release,
(None, None) => true,
_ => false,
}
}
pub fn new_major(&self) -> Option<Self> {
match self.major.checked_add(1) {
Some(new_major) => Some(Self::new(new_major, 0, 0)),
None => None,
}
}
pub fn new_major_with_last_details(&self) -> Option<Self> {
self.new_major().map(|mut result| {
result.pre_release = self.pre_release.clone();
result.build_metadata = self.build_metadata.clone();
result
})
}
pub fn new_minor(&self) -> Option<Self> {
match self.minor.checked_add(1) {
Some(new_minor) => Some(Self::new(self.major, new_minor, 0)),
None => None,
}
}
pub fn new_minor_with_last_details(&self) -> Option<Self> {
self.new_minor().map(|mut result| {
result.pre_release = self.pre_release.clone();
result.build_metadata = self.build_metadata.clone();
result
})
}
pub fn new_patch(&self) -> Option<Self> {
match self.patch.checked_add(1) {
Some(new_patch) => Some(Self::new(self.major, self.minor, new_patch)),
None => None,
}
}
pub fn new_patch_with_last_details(&self) -> Option<Self> {
self.new_patch().map(|mut result| {
result.pre_release = self.pre_release.clone();
result.build_metadata = self.build_metadata.clone();
result
})
}
pub fn new_pre_release<S>(&self, pre_release: S, keep_build_metadata: bool) -> Result<Self> where S: AsRef<str> {
let pre_release = pre_release.as_ref();
let mut s = alloc::format!(
"{major}.{minor}.{patch}{pre_release_starter}{pre_release}",
major=self.major, minor=self.minor, patch=self.patch, pre_release_starter=self::PRE_RELEASE_STARTER, pre_release=pre_release,
);
if keep_build_metadata {
if let Some(build_metadata) = self.build_metadata.as_ref() {
s.push(self::BUILD_METADATA_STARTER);
s.push_str(build_metadata);
}
}
Self::parse(s)
}
pub fn drop_pre_release(&self) -> Self {
Self {
major: self.major,
minor: self.minor,
patch: self.patch,
build_metadata: self.build_metadata.clone(),
pre_release: None,
}
}
pub fn new_build_metadata<S>(&self, build_metadata: S, keep_pre_release: bool) -> Result<Self> where S: AsRef<str> {
let build_metadata = build_metadata.as_ref();
let mut s = alloc::format!("{major}.{minor}.{patch}", major=self.major, minor=self.minor, patch=self.patch);
if keep_pre_release {
if let Some(pre_release) = self.pre_release.as_ref() {
s.push(self::PRE_RELEASE_STARTER);
s.push_str(pre_release);
}
}
s.push(self::BUILD_METADATA_STARTER);
s.push_str(build_metadata);
Self::parse(s)
}
pub fn drop_build_metadata(&self) -> Self {
Self {
major: self.major,
minor: self.minor,
patch: self.patch,
build_metadata: None,
pre_release: self.pre_release.clone(),
}
}
pub fn to_short_format(&self) -> String {
let mut result = match self.patch {
0 => match self.minor {
0 => self.major.to_string(),
_ => alloc::format!("{}.{}", self.major, self.minor),
},
_ => return self.to_string(),
};
if let Some(pre_release) = self.pre_release.as_ref() {
result.push(self::PRE_RELEASE_STARTER);
result.push_str(pre_release);
}
if let Some(build_metadata) = self.build_metadata.as_ref() {
result.push(self::BUILD_METADATA_STARTER);
result.push_str(build_metadata);
}
result
}
}
impl PartialEq for Semver {
fn eq(&self, other: &Self) -> bool {
self.cmp(&other) == Ordering::Equal
}
}
impl Ord for Semver {
fn cmp(&self, other: &Self) -> Ordering {
match self.major.cmp(&other.major) {
Ordering::Equal => match self.minor.cmp(&other.minor) {
Ordering::Equal => match self.patch.cmp(&other.patch) {
Ordering::Equal => (),
x => return x,
},
x => return x,
},
x => return x,
};
match (self.pre_release.as_ref(), other.pre_release.as_ref()) {
(None, None) => return Ordering::Equal,
(None, Some(_)) => return Ordering::Greater,
(Some(_), None) => return Ordering::Less,
(Some(self_pre_release), Some(other_pre_release)) => {
let mut self_pre_release = self_pre_release.split('.');
let mut other_pre_release = other_pre_release.split('.');
loop {
match (self_pre_release.next(), other_pre_release.next()) {
(None, None) => return Ordering::Equal,
(None, Some(_)) => return Ordering::Less,
(Some(_), None) => return Ordering::Greater,
(Some(self_field), Some(other_field)) => {
let self_field_is_number = self_field.chars().any(|c| c < '0' || c > '9') == false;
let other_field_is_number = other_field.chars().any(|c| c < '0' || c > '9') == false;
match (self_field_is_number, other_field_is_number) {
(true, true) => match self_field.len().cmp(&other_field.len()) {
Ordering::Equal => match self_field.cmp(&other_field) {
Ordering::Equal => (),
x => return x,
},
x => return x,
},
(true, false) => return Ordering::Less,
(false, true) => return Ordering::Greater,
(false, false) => match self_field.cmp(&other_field) {
Ordering::Equal => (),
x => return x,
},
};
},
};
}
},
};
}
}
impl PartialOrd for Semver {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Hash for Semver {
fn hash<H>(&self, state: &mut H) where H: Hasher {
self.major.hash(state);
self.minor.hash(state);
self.patch.hash(state);
if let Some(pre_release) = self.pre_release.as_ref() {
pre_release.hash(state);
}
}
}
impl FromStr for Semver {
type Err = Error;
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
Parser::parse(s, false)
}
}
impl Display for Semver {
fn fmt(&self, f: &mut Formatter) -> core::result::Result<(), fmt::Error> {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)?;
if let Some(pre_release) = self.pre_release.as_ref() {
write!(f, "{}{}", self::PRE_RELEASE_STARTER, pre_release)?;
}
if let Some(build_metadata) = self.build_metadata.as_ref() {
write!(f, "{}{}", self::BUILD_METADATA_STARTER, build_metadata)?;
}
Ok(())
}
}
impl<T> From<T> for Semver where T: Into<u64> {
fn from(value: T) -> Self {
Self::new(value.into(), u64::min_value(), u64::min_value())
}
}
#[test]
fn test_semver_internal_stuff() {
use alloc::string::ToString;
assert_eq!(Semver::new(0, 0, 0).major.wrapping_sub(1).to_string().len(), MAX_STR_LEN_OF_A_VERSION_NUMBER);
}