use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde_with::{DeserializeFromStr, SerializeDisplay};
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))]
pub struct PkgName {
pkgname: String,
split: usize,
}
#[must_use]
pub fn pkgbase(pkgname: &str) -> &str {
pkgname.rsplit_once('-').map_or(pkgname, |(b, _)| b)
}
#[must_use]
pub fn pkgversion(pkgname: &str) -> &str {
pkgname.rsplit_once('-').map_or("", |(_, v)| v)
}
#[must_use]
pub fn pkgversion_norev(pkgversion: &str) -> &str {
pkgversion
.rsplit_once("nb")
.map_or(pkgversion, |(before, _)| before)
}
#[must_use]
pub fn pkgrevision(pkgversion: &str) -> Option<i64> {
pkgversion
.rsplit_once("nb")
.map(|(_, v)| v.parse::<i64>().unwrap_or(0))
}
impl PkgName {
#[must_use]
pub fn new(pkgname: &str) -> Self {
let split = pkgname.rfind('-').unwrap_or(pkgname.len());
Self {
pkgname: pkgname.to_string(),
split,
}
}
#[must_use]
pub fn pkgname(&self) -> &str {
&self.pkgname
}
#[must_use]
pub fn pkgbase(&self) -> &str {
&self.pkgname[..self.split]
}
#[must_use]
pub fn pkgversion(&self) -> &str {
if self.split < self.pkgname.len() {
&self.pkgname[self.split + 1..]
} else {
""
}
}
#[must_use]
pub fn pkgversion_norev(&self) -> &str {
pkgversion_norev(self.pkgversion())
}
#[must_use]
pub fn pkgrevision(&self) -> Option<i64> {
pkgrevision(self.pkgversion())
}
}
impl From<&str> for PkgName {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for PkgName {
fn from(s: String) -> Self {
Self::new(&s)
}
}
impl From<&String> for PkgName {
fn from(s: &String) -> Self {
Self::new(s)
}
}
impl std::fmt::Display for PkgName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.pkgname)
}
}
impl PartialEq<str> for PkgName {
fn eq(&self, other: &str) -> bool {
self.pkgname == other
}
}
impl PartialEq<&str> for PkgName {
fn eq(&self, other: &&str) -> bool {
&self.pkgname == other
}
}
impl PartialEq<String> for PkgName {
fn eq(&self, other: &String) -> bool {
&self.pkgname == other
}
}
impl FromStr for PkgName {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::new(s))
}
}
impl AsRef<str> for PkgName {
fn as_ref(&self) -> &str {
&self.pkgname
}
}
impl Borrow<str> for PkgName {
fn borrow(&self) -> &str {
&self.pkgname
}
}
impl Hash for PkgName {
fn hash<H: Hasher>(&self, state: &mut H) {
self.pkgname.hash(state);
}
}
impl crate::kv::FromKv for PkgName {
fn from_kv(value: &str, _span: crate::kv::Span) -> crate::kv::Result<Self> {
Ok(Self::new(value))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pkgname_full() {
let pkg = PkgName::new("mktool-1.3.2nb2");
assert_eq!(format!("{pkg}"), "mktool-1.3.2nb2");
assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
assert_eq!(pkg.pkgbase(), "mktool");
assert_eq!(pkg.pkgversion(), "1.3.2nb2");
assert_eq!(pkg.pkgrevision(), Some(2));
}
#[test]
fn pkgname_broken_pkgrevision() {
let pkg = PkgName::new("mktool-1nb3alpha2nb");
assert_eq!(pkg.pkgbase(), "mktool");
assert_eq!(pkg.pkgversion(), "1nb3alpha2nb");
assert_eq!(pkg.pkgrevision(), Some(0));
}
#[test]
fn pkgname_no_version() {
let pkg = PkgName::new("mktool");
assert_eq!(pkg.pkgbase(), "mktool");
assert_eq!(pkg.pkgversion(), "");
assert_eq!(pkg.pkgrevision(), None);
}
#[test]
fn pkgname_from() {
let pkg = PkgName::from("mktool-1.3.2nb2");
assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
let pkg = PkgName::from(String::from("mktool-1.3.2nb2"));
assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
let s = String::from("mktool-1.3.2nb2");
let pkg = PkgName::from(&s);
assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
}
#[test]
fn pkgname_from_str() -> Result<(), std::convert::Infallible> {
use std::str::FromStr;
let pkg = PkgName::from_str("mktool-1.3.2nb2")?;
assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
let pkg: PkgName = "foo-2.0".parse()?;
assert_eq!(pkg.pkgbase(), "foo");
Ok(())
}
#[test]
fn pkgname_partial_eq() {
let pkg = PkgName::new("mktool-1.3.2nb2");
assert_eq!(pkg, *"mktool-1.3.2nb2");
assert_eq!(pkg, "mktool-1.3.2nb2");
assert_eq!(pkg, "mktool-1.3.2nb2".to_string());
assert_ne!(pkg, "notmktool-1.0");
}
#[test]
fn pkgname_as_ref() {
let pkg = PkgName::new("mktool-1.3.2nb2");
let s: &str = pkg.as_ref();
assert_eq!(s, "mktool-1.3.2nb2");
fn takes_asref(s: impl AsRef<str>) -> usize {
s.as_ref().len()
}
assert_eq!(takes_asref(&pkg), 15);
}
#[test]
fn pkgname_borrow() {
use std::collections::HashMap;
let mut map: HashMap<PkgName, i32> = HashMap::new();
map.insert(PkgName::new("foo-1.0"), 42);
assert_eq!(map.get("foo-1.0"), Some(&42));
assert_eq!(map.get("bar-2.0"), None);
}
#[test]
#[cfg(feature = "serde")]
fn pkgname_serde() -> Result<(), serde_json::Error> {
let pkg = PkgName::new("mktool-1.3.2nb2");
let se = serde_json::to_string(&pkg)?;
let de: PkgName = serde_json::from_str(&se)?;
assert_eq!(se, "\"mktool-1.3.2nb2\"");
assert_eq!(pkg, de);
assert_eq!(de.pkgname(), "mktool-1.3.2nb2");
assert_eq!(de.pkgbase(), "mktool");
assert_eq!(de.pkgversion(), "1.3.2nb2");
assert_eq!(de.pkgrevision(), Some(2));
Ok(())
}
}