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