1use itertools::Itertools;
2
3use serde::{de, Deserialize, Deserializer, Serialize};
4use std::{cmp::Ordering, fmt::Display, str::FromStr};
5use thiserror::Error;
6
7mod outdated;
8mod version;
9
10pub use outdated::*;
11pub use version::{
12 PackageVersion, PackageVersionParseError, PackageVersionReq, PackageVersionReqError, SpecRev,
13 VersionReqToVersionError,
14};
15
16pub(crate) use version::SpecRevIterator;
17
18use crate::{
19 lockfile::RemotePackageSourceUrl,
20 lua_rockspec::{DisplayAsLuaKV, DisplayLuaKV, DisplayLuaValue},
21 package::version::HasModRev,
22 remote_package_source::RemotePackageSource,
23 rockspec::lua_dependency::LuaDependencySpec,
24 variables::{GetVariableError, HasVariables},
25};
26
27#[derive(Debug, Error)]
28pub enum PackageSpecFromPackageReqError {
29 #[error("invalid version for rock {rock}: {err}")]
30 Version {
31 rock: PackageName,
32 err: VersionReqToVersionError,
33 },
34}
35
36#[derive(Clone, Debug, PartialEq, Eq)]
38#[cfg_attr(feature = "clap", derive(clap::Args))]
39pub struct PackageSpec {
40 name: PackageName,
41 version: PackageVersion,
42}
43
44impl PackageSpec {
45 pub fn new(name: PackageName, version: PackageVersion) -> Self {
46 Self { name, version }
47 }
48 pub fn parse(name: String, version: String) -> Result<Self, PackageVersionParseError> {
49 Ok(Self::new(
50 PackageName::new(name),
51 PackageVersion::parse(&version)?,
52 ))
53 }
54 pub fn name(&self) -> &PackageName {
55 &self.name
56 }
57 pub fn version(&self) -> &PackageVersion {
58 &self.version
59 }
60 pub fn into_package_req(self) -> PackageReq {
62 PackageReq {
63 name: self.name,
64 version_req: self.version.into_version_req(),
65 }
66 }
67}
68
69impl TryFrom<PackageReq> for PackageSpec {
70 type Error = PackageSpecFromPackageReqError;
71
72 fn try_from(value: PackageReq) -> Result<Self, Self::Error> {
73 let name = value.name;
74 let version = value.version_req.try_into().map_err(|err| {
75 PackageSpecFromPackageReqError::Version {
76 rock: name.clone(),
77 err,
78 }
79 })?;
80 Ok(Self { name, version })
81 }
82}
83
84impl<'de> Deserialize<'de> for PackageSpec {
85 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
86 where
87 D: Deserializer<'de>,
88 {
89 let (name, version) = <_>::deserialize(deserializer)?;
90 Self::parse(name, version).map_err(serde::de::Error::custom)
91 }
92}
93
94impl HasVariables for PackageSpec {
95 fn get_variable(&self, input: &str) -> Result<Option<String>, GetVariableError> {
96 Ok(match input {
97 "PACKAGE" => Some(self.name.to_string()),
98 "VERSION" => Some(self.version.to_modrev_string()),
99 _ => None,
100 })
101 }
102}
103
104#[derive(Clone, Debug)]
105pub(crate) struct RemotePackage {
106 pub package: PackageSpec,
107 pub source: RemotePackageSource,
108 pub source_url: Option<RemotePackageSourceUrl>,
110}
111
112impl RemotePackage {
113 pub fn new(
114 package: PackageSpec,
115 source: RemotePackageSource,
116 source_url: Option<RemotePackageSourceUrl>,
117 ) -> Self {
118 Self {
119 package,
120 source,
121 source_url,
122 }
123 }
124}
125
126#[derive(PartialEq, Eq, Hash, Clone, Debug)]
127pub(crate) enum RemotePackageType {
128 Rockspec,
129 Src,
130 Binary,
131}
132
133impl Ord for RemotePackageType {
134 fn cmp(&self, other: &Self) -> Ordering {
135 match (self, other) {
137 (Self::Binary, Self::Binary)
138 | (Self::Rockspec, Self::Rockspec)
139 | (Self::Src, Self::Src) => Ordering::Equal,
140
141 (Self::Binary, _) => Ordering::Greater,
142 (_, Self::Binary) => Ordering::Less,
143 (Self::Rockspec, Self::Src) => Ordering::Greater,
144 (Self::Src, Self::Rockspec) => Ordering::Less,
145 }
146 }
147}
148
149impl PartialOrd for RemotePackageType {
150 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
151 Some(self.cmp(other))
152 }
153}
154
155#[derive(Clone)]
156pub struct RemotePackageTypeFilterSpec {
157 pub rockspec: bool,
159 pub src: bool,
161 pub binary: bool,
163}
164
165impl Default for RemotePackageTypeFilterSpec {
166 fn default() -> Self {
167 Self {
168 rockspec: true,
169 src: true,
170 binary: true,
171 }
172 }
173}
174
175#[derive(Error, Debug)]
176pub enum ParseRemotePackageError {
177 #[error("unable to parse package {0}. expected format: `name@version`")]
178 InvalidInput(String),
179 #[error("unable to parse package {package_str}:\n{error}")]
180 InvalidPackageVersion {
181 #[source]
182 error: PackageVersionParseError,
183 package_str: String,
184 },
185}
186
187impl FromStr for PackageSpec {
188 type Err = ParseRemotePackageError;
189
190 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
191 let (name, version) = s
192 .split_once('@')
193 .ok_or_else(|| ParseRemotePackageError::InvalidInput(s.to_string()))?;
194
195 Self::parse(name.to_string(), version.to_string()).map_err(|error| {
196 ParseRemotePackageError::InvalidPackageVersion {
197 error,
198 package_str: s.to_string(),
199 }
200 })
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, Eq, Hash)]
206#[cfg_attr(feature = "clap", derive(clap::Args))]
207pub struct PackageReq {
208 pub(crate) name: PackageName,
210 pub(crate) version_req: PackageVersionReq,
212}
213
214impl PackageReq {
215 pub fn new(name: String, version: Option<String>) -> Result<Self, PackageVersionReqError> {
216 let version_req = match version {
217 Some(version_req_str) => PackageVersionReq::parse(version_req_str.as_str())?,
218 None => PackageVersionReq::any(),
219 };
220 Ok(Self {
221 name: PackageName::new(name),
222 version_req,
223 })
224 }
225
226 pub unsafe fn new_unchecked(name: String, version: Option<String>) -> Self {
231 Self::new(name, version).unwrap_unchecked()
232 }
233
234 pub fn parse(pkg_constraints: &str) -> Result<Self, PackageReqParseError> {
235 Self::from_str(pkg_constraints)
236 }
237 pub fn name(&self) -> &PackageName {
238 &self.name
239 }
240 pub fn version_req(&self) -> &PackageVersionReq {
241 &self.version_req
242 }
243 pub fn matches(&self, package: &PackageSpec) -> bool {
245 self.name == package.name && self.version_req.matches(&package.version)
246 }
247}
248
249impl Display for PackageReq {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 if self.version_req.is_any() {
252 self.name.fmt(f)
253 } else {
254 f.write_str(format!("{}{}", self.name, self.version_req).as_str())
255 }
256 }
257}
258
259impl From<PackageSpec> for PackageReq {
260 fn from(value: PackageSpec) -> Self {
261 value.into_package_req()
262 }
263}
264
265impl From<PackageName> for PackageReq {
266 fn from(name: PackageName) -> Self {
267 Self {
268 name,
269 version_req: PackageVersionReq::any(),
270 }
271 }
272}
273
274pub(crate) struct Dependencies<'a>(pub(crate) &'a Vec<LuaDependencySpec>);
276pub(crate) struct BuildDependencies<'a>(pub(crate) &'a Vec<LuaDependencySpec>);
277
278impl DisplayAsLuaKV for Dependencies<'_> {
279 fn display_lua(&self) -> DisplayLuaKV {
280 DisplayLuaKV {
281 key: "dependencies".to_string(),
282 value: DisplayLuaValue::List(
283 self.0
284 .iter()
285 .map(|req| DisplayLuaValue::String(req.to_string()))
286 .collect(),
287 ),
288 }
289 }
290}
291
292impl DisplayAsLuaKV for BuildDependencies<'_> {
293 fn display_lua(&self) -> DisplayLuaKV {
294 DisplayLuaKV {
295 key: "build_dependencies".to_string(),
296 value: DisplayLuaValue::List(
297 self.0
298 .iter()
299 .map(|req| DisplayLuaValue::String(req.to_string()))
300 .collect(),
301 ),
302 }
303 }
304}
305
306#[derive(Error, Debug)]
307pub enum PackageReqParseError {
308 #[error("could not parse dependency name from {0}")]
309 InvalidDependencyName(String),
310 #[error("could not parse version requirement in '{str}': {error}")]
311 InvalidPackageVersionReq {
312 #[source]
313 error: PackageVersionReqError,
314 str: String,
315 },
316}
317
318impl FromStr for PackageReq {
319 type Err = PackageReqParseError;
320
321 fn from_str(str: &str) -> Result<Self, PackageReqParseError> {
322 let rock_name_str = str
323 .chars()
324 .peeking_take_while(|t| t.is_alphanumeric() || matches!(t, '-' | '_' | '.'))
325 .collect::<String>();
326
327 if rock_name_str.is_empty() {
328 return Err(PackageReqParseError::InvalidDependencyName(str.to_string()));
329 }
330
331 let constraints = str.trim_start_matches(&rock_name_str).trim();
332 let version_req = match constraints {
333 "" => PackageVersionReq::any(),
334 constraints => PackageVersionReq::parse(constraints.trim_start()).map_err(|error| {
335 PackageReqParseError::InvalidPackageVersionReq {
336 error,
337 str: str.to_string(),
338 }
339 })?,
340 };
341 Ok(Self {
342 name: PackageName::new(rock_name_str),
343 version_req,
344 })
345 }
346}
347
348impl<'de> Deserialize<'de> for PackageReq {
349 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
350 where
351 D: Deserializer<'de>,
352 {
353 let s = String::deserialize(deserializer)?;
354 Self::from_str(&s).map_err(de::Error::custom)
355 }
356}
357
358#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
360pub struct PackageName(String);
361
362impl PackageName {
363 pub fn new(name: String) -> Self {
364 Self(name.to_lowercase())
365 }
366}
367
368impl<'de> Deserialize<'de> for PackageName {
369 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
370 where
371 D: serde::Deserializer<'de>,
372 {
373 Ok(PackageName::new(String::deserialize(deserializer)?))
374 }
375}
376
377impl Serialize for PackageName {
378 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
379 where
380 S: serde::Serializer,
381 {
382 self.0.serialize(serializer)
383 }
384}
385
386impl From<&str> for PackageName {
387 fn from(value: &str) -> Self {
388 Self::new(value.into())
389 }
390}
391
392impl Display for PackageName {
393 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
394 f.write_str(self.0.as_str())
395 }
396}
397
398#[derive(Debug)]
399pub struct PackageNameList(Vec<PackageName>);
400
401impl PackageNameList {
402 pub(crate) fn new(package_names: Vec<PackageName>) -> Self {
403 Self(package_names)
404 }
405}
406
407impl Display for PackageNameList {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 f.write_str(self.0.iter().map(|pkg| pkg.to_string()).join(", ").as_str())
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416
417 #[tokio::test]
418 async fn parse_name() {
419 let mut package_name: PackageName = "neorg".into();
420 assert_eq!(package_name.to_string(), "neorg");
421 package_name = "LuaFileSystem".into();
422 assert_eq!(package_name.to_string(), "luafilesystem");
423 }
424
425 #[tokio::test]
426 async fn parse_lua_package() {
427 let neorg = PackageSpec::parse("neorg".into(), "1.0.0".into()).unwrap();
428 let expected_version = PackageVersion::parse("1.0.0").unwrap();
429 assert_eq!(neorg.name().to_string(), "neorg");
430 assert!(matches!(
431 neorg.version().cmp(&expected_version),
432 std::cmp::Ordering::Equal
433 ));
434 let neorg = PackageSpec::parse("neorg".into(), "1.0".into()).unwrap();
435 assert!(matches!(
436 neorg.version().cmp(&expected_version),
437 std::cmp::Ordering::Equal
438 ));
439 let neorg = PackageSpec::parse("neorg".into(), "1".into()).unwrap();
440 assert!(matches!(
441 neorg.version().cmp(&expected_version),
442 std::cmp::Ordering::Equal
443 ));
444 }
445
446 #[tokio::test]
447 async fn parse_lua_package_req() {
448 let mut package_req = PackageReq::new("foo".into(), Some("1.0.0".into())).unwrap();
449 assert!(package_req.matches(&PackageSpec::parse("foo".into(), "1.0.0".into()).unwrap()));
450 assert!(!package_req.matches(&PackageSpec::parse("bar".into(), "1.0.0".into()).unwrap()));
451 assert!(!package_req.matches(&PackageSpec::parse("foo".into(), "2.0.0".into()).unwrap()));
452 package_req = PackageReq::new("foo".into(), Some(">= 1.0.0".into())).unwrap();
453 assert!(package_req.matches(&PackageSpec::parse("foo".into(), "2.0.0".into()).unwrap()));
454 let package_req: PackageReq = "lua >= 5.1".parse().unwrap();
455 assert_eq!(package_req.name.to_string(), "lua");
456 let package_req: PackageReq = "lua>=5.1".parse().unwrap();
457 assert_eq!(package_req.name.to_string(), "lua");
458 let package_req: PackageReq = "toml-edit >= 0.1.0".parse().unwrap();
459 assert_eq!(package_req.name.to_string(), "toml-edit");
460 let package_req: PackageReq = "plugin.nvim >= 0.1.0".parse().unwrap();
461 assert_eq!(package_req.name.to_string(), "plugin.nvim");
462 let package_req: PackageReq = "lfs".parse().unwrap();
463 assert_eq!(package_req.name.to_string(), "lfs");
464 let package_req: PackageReq = "neorg 1.0.0".parse().unwrap();
465 assert_eq!(package_req.name.to_string(), "neorg");
466 let neorg = PackageSpec::parse("neorg".into(), "1.0.0".into()).unwrap();
467 assert!(package_req.matches(&neorg));
468 let neorg = PackageSpec::parse("neorg".into(), "2.0.0".into()).unwrap();
469 assert!(!package_req.matches(&neorg));
470 let package_req: PackageReq = "neorg 2.0.0".parse().unwrap();
471 assert!(package_req.matches(&neorg));
472 let package_req: PackageReq = "neorg = 2.0.0".parse().unwrap();
473 assert!(package_req.matches(&neorg));
474 let package_req: PackageReq = "neorg == 2.0.0".parse().unwrap();
475 assert!(package_req.matches(&neorg));
476 let package_req: PackageReq = "neorg = 2.0.0".parse().unwrap();
477 assert!(package_req.matches(&neorg));
478 let package_req: PackageReq = "neorg >= 1.0, < 2.0".parse().unwrap();
479 let neorg = PackageSpec::parse("neorg".into(), "1.5".into()).unwrap();
480 assert!(package_req.matches(&neorg));
481 let package_req: PackageReq = "neorg > 1.0, < 2.0".parse().unwrap();
482 let neorg = PackageSpec::parse("neorg".into(), "1.11.0".into()).unwrap();
483 assert!(package_req.matches(&neorg));
484 let neorg = PackageSpec::parse("neorg".into(), "3.0.0".into()).unwrap();
485 assert!(!package_req.matches(&neorg));
486 let neorg = PackageSpec::parse("neorg".into(), "0.5".into()).unwrap();
487 assert!(!package_req.matches(&neorg));
488 let package_req: PackageReq = "neorg ~> 1".parse().unwrap();
489 assert!(!package_req.matches(&neorg));
490 let neorg = PackageSpec::parse("neorg".into(), "3".into()).unwrap();
491 assert!(!package_req.matches(&neorg));
492 let neorg = PackageSpec::parse("neorg".into(), "1.5".into()).unwrap();
493 assert!(package_req.matches(&neorg));
494 let package_req: PackageReq = "neorg ~> 1.4".parse().unwrap();
495 let neorg = PackageSpec::parse("neorg".into(), "1.3".into()).unwrap();
496 assert!(!package_req.matches(&neorg));
497 let neorg = PackageSpec::parse("neorg".into(), "1.5".into()).unwrap();
498 assert!(!package_req.matches(&neorg));
499 let neorg = PackageSpec::parse("neorg".into(), "1.4.10".into()).unwrap();
500 assert!(package_req.matches(&neorg));
501 let neorg = PackageSpec::parse("neorg".into(), "1.4".into()).unwrap();
502 assert!(package_req.matches(&neorg));
503 let package_req: PackageReq = "neorg ~> 1.0.5".parse().unwrap();
504 let neorg = PackageSpec::parse("neorg".into(), "1.0.4".into()).unwrap();
505 assert!(!package_req.matches(&neorg));
506 let neorg = PackageSpec::parse("neorg".into(), "1.0.5".into()).unwrap();
507 assert!(package_req.matches(&neorg));
508 let neorg = PackageSpec::parse("neorg".into(), "1.0.6".into()).unwrap();
509 assert!(!package_req.matches(&neorg));
510 let package_req: PackageReq = "lua-utils.nvim ~> 1.1-1".parse().unwrap();
512 let lua_utils = PackageSpec::parse("lua-utils.nvim".into(), "1.1.4".into()).unwrap();
513 assert!(package_req.matches(&lua_utils));
514 let lua_utils = PackageSpec::parse("lua-utils.nvim".into(), "1.1.5".into()).unwrap();
515 assert!(package_req.matches(&lua_utils));
516 let lua_utils = PackageSpec::parse("lua-utils.nvim".into(), "1.2-1".into()).unwrap();
517 assert!(!package_req.matches(&lua_utils));
518 }
519
520 #[tokio::test]
521 pub async fn remote_package_type_priorities() {
522 let rock_types = vec![
523 RemotePackageType::Binary,
524 RemotePackageType::Src,
525 RemotePackageType::Rockspec,
526 ];
527 assert_eq!(
528 rock_types.into_iter().sorted().collect_vec(),
529 vec![
530 RemotePackageType::Src,
531 RemotePackageType::Rockspec,
532 RemotePackageType::Binary,
533 ]
534 );
535 }
536
537 #[tokio::test]
538 async fn package_spec_req_roundtrips() {
539 let spec = PackageSpec::new("foo".into(), "1.0.0-1".parse().unwrap());
540 let req: PackageReq = spec.clone().into();
541 assert_eq!(spec, req.try_into().unwrap());
542
543 let spec = PackageSpec::new("foo".into(), "1.0.0".parse().unwrap());
544 let req: PackageReq = spec.clone().into();
545 assert_eq!(spec, req.try_into().unwrap());
546 }
547
548 #[tokio::test]
549 async fn package_req_spec_roundtrips() {
550 let req: PackageReq = "foo@1.0.0".parse().unwrap();
551 let spec: PackageSpec = req.clone().try_into().unwrap();
552 assert_eq!(req, spec.into());
553
554 let req: PackageReq = "foo@1.0.0-1".parse().unwrap();
555 let spec: PackageSpec = req.clone().try_into().unwrap();
556 assert_eq!(req, spec.into());
557 }
558
559 #[tokio::test]
560 async fn package_spec_parses_same_as_name_version() {
561 let req: PackageReq = "foo@1.0.0".parse().unwrap();
562 let spec: PackageSpec = req.clone().try_into().unwrap();
563 let name: PackageName = "foo".into();
564 let version: PackageVersion = "1.0.0".parse().unwrap();
565 assert_eq!(spec, PackageSpec::new(name, version));
566
567 let req: PackageReq = "foo@1.0.0-1".parse().unwrap();
568 let spec: PackageSpec = req.clone().try_into().unwrap();
569 let name: PackageName = "foo".into();
570 let version: PackageVersion = "1.0.0-1".parse().unwrap();
571 assert_eq!(spec, PackageSpec::new(name, version));
572 }
573}