1#[cfg(feature = "serde")]
18use serde_with::{DeserializeFromStr, SerializeDisplay};
19
20#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
74#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))]
75pub struct PkgName {
76 pkgname: String,
77 pkgbase: String,
78 pkgversion: String,
79 pkgrevision: Option<i64>,
80}
81
82impl PkgName {
83 #[must_use]
87 pub fn new(pkgname: &str) -> Self {
88 let (pkgbase, pkgversion) = match pkgname.rsplit_once('-') {
89 Some((b, v)) => (String::from(b), String::from(v)),
90 None => (String::from(pkgname), String::new()),
91 };
92 let pkgrevision = match pkgversion.rsplit_once("nb") {
93 Some((_, v)) => v.parse::<i64>().ok().or(Some(0)),
94 None => None,
95 };
96 Self {
97 pkgname: pkgname.to_string(),
98 pkgbase,
99 pkgversion,
100 pkgrevision,
101 }
102 }
103
104 #[must_use]
109 pub fn pkgname(&self) -> &str {
110 &self.pkgname
111 }
112
113 #[must_use]
119 pub fn pkgbase(&self) -> &str {
120 &self.pkgbase
121 }
122
123 #[must_use]
130 pub fn pkgversion(&self) -> &str {
131 &self.pkgversion
132 }
133
134 #[must_use]
141 pub const fn pkgrevision(&self) -> Option<i64> {
142 self.pkgrevision
143 }
144}
145
146impl From<&str> for PkgName {
147 fn from(s: &str) -> Self {
148 Self::new(s)
149 }
150}
151
152impl From<String> for PkgName {
153 fn from(s: String) -> Self {
154 Self::new(&s)
155 }
156}
157
158impl From<&String> for PkgName {
159 fn from(s: &String) -> Self {
160 Self::new(s)
161 }
162}
163
164impl std::fmt::Display for PkgName {
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 write!(f, "{}", self.pkgname)
167 }
168}
169
170impl PartialEq<str> for PkgName {
171 fn eq(&self, other: &str) -> bool {
172 self.pkgname == other
173 }
174}
175
176impl PartialEq<&str> for PkgName {
177 fn eq(&self, other: &&str) -> bool {
178 &self.pkgname == other
179 }
180}
181
182impl PartialEq<String> for PkgName {
183 fn eq(&self, other: &String) -> bool {
184 &self.pkgname == other
185 }
186}
187
188impl std::str::FromStr for PkgName {
189 type Err = std::convert::Infallible;
190
191 fn from_str(s: &str) -> Result<Self, Self::Err> {
192 Ok(Self::new(s))
193 }
194}
195
196impl crate::kv::FromKv for PkgName {
197 fn from_kv(value: &str, _span: crate::kv::Span) -> crate::kv::Result<Self> {
198 Ok(Self::new(value))
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn pkgname_full() {
208 let pkg = PkgName::new("mktool-1.3.2nb2");
209 assert_eq!(format!("{pkg}"), "mktool-1.3.2nb2");
210 assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
211 assert_eq!(pkg.pkgbase(), "mktool");
212 assert_eq!(pkg.pkgversion(), "1.3.2nb2");
213 assert_eq!(pkg.pkgrevision(), Some(2));
214 }
215
216 #[test]
217 fn pkgname_broken_pkgrevision() {
218 let pkg = PkgName::new("mktool-1nb3alpha2nb");
219 assert_eq!(pkg.pkgbase(), "mktool");
220 assert_eq!(pkg.pkgversion(), "1nb3alpha2nb");
221 assert_eq!(pkg.pkgrevision(), Some(0));
222 }
223
224 #[test]
225 fn pkgname_no_version() {
226 let pkg = PkgName::new("mktool");
227 assert_eq!(pkg.pkgbase(), "mktool");
228 assert_eq!(pkg.pkgversion(), "");
229 assert_eq!(pkg.pkgrevision(), None);
230 }
231
232 #[test]
233 fn pkgname_from() {
234 let pkg = PkgName::from("mktool-1.3.2nb2");
235 assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
236 let pkg = PkgName::from(String::from("mktool-1.3.2nb2"));
237 assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
238 let s = String::from("mktool-1.3.2nb2");
239 let pkg = PkgName::from(&s);
240 assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
241 }
242
243 #[test]
244 fn pkgname_from_str() {
245 use std::str::FromStr;
246
247 let pkg = PkgName::from_str("mktool-1.3.2nb2").unwrap();
248 assert_eq!(pkg.pkgname(), "mktool-1.3.2nb2");
249
250 let pkg: PkgName = "foo-2.0".parse().unwrap();
251 assert_eq!(pkg.pkgbase(), "foo");
252 }
253
254 #[test]
255 fn pkgname_partial_eq() {
256 let pkg = PkgName::new("mktool-1.3.2nb2");
257 assert_eq!(pkg, *"mktool-1.3.2nb2");
258 assert_eq!(pkg, "mktool-1.3.2nb2");
259 assert_eq!(pkg, "mktool-1.3.2nb2".to_string());
260 assert_ne!(pkg, "notmktool-1.0");
261 }
262
263 #[test]
264 #[cfg(feature = "serde")]
265 fn pkgname_serde() {
266 let pkg = PkgName::new("mktool-1.3.2nb2");
267 let se = serde_json::to_string(&pkg).unwrap();
268 let de: PkgName = serde_json::from_str(&se).unwrap();
269 assert_eq!(se, "\"mktool-1.3.2nb2\"");
270 assert_eq!(pkg, de);
271 assert_eq!(de.pkgname(), "mktool-1.3.2nb2");
272 assert_eq!(de.pkgbase(), "mktool");
273 assert_eq!(de.pkgversion(), "1.3.2nb2");
274 assert_eq!(de.pkgrevision(), Some(2));
275 }
276}